Add initial browser test suite using phantomjs for headless testing and jasmine

This commit is contained in:
Luke Page
2012-12-15 10:15:53 +00:00
parent ee4c0c8660
commit 6554117643
19 changed files with 3678 additions and 50 deletions

View File

@@ -0,0 +1,19 @@
var path = require('path'),
fs = require('fs'),
sys = require('util'),
output = '<html><head>\n';
fs.readdirSync(path.join('test/less/', '')).forEach(function (file) {
if (! /\.less/.test(file)) { return; }
var name = path.basename(file, '.less');
if (name === "javascript" || name === "urls") { return; }
output += '<link id="original-less:less-'+name+'" rel="stylesheet/less" type="text/css" href="http://localhost:8081/' + path.join('less', name) + '.less' +'">\n';
output += '<link id="expected-less:less-'+name+'" rel="stylesheet" type="text/css" href="http://localhost:8081/' + path.join('css', name) + '.css' + '">\n';
});
output += String(fs.readFileSync(path.join('test/browser', 'template.htm'))).replace("{runner-name}", "main");
fs.writeFileSync(path.join('test/browser', 'test-runner-main.htm'), output);

View File

@@ -0,0 +1,681 @@
jasmine.HtmlReporterHelpers = {};
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) {
el.appendChild(child);
}
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
var results = child.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
return status;
};
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
var parentDiv = this.dom.summary;
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
var parent = child[parentSuite];
if (parent) {
if (typeof this.views.suites[parent.id] == 'undefined') {
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
}
parentDiv = this.views.suites[parent.id].element;
}
parentDiv.appendChild(childElement);
};
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
for(var fn in jasmine.HtmlReporterHelpers) {
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
}
};
jasmine.HtmlReporter = function(_doc) {
var self = this;
var doc = _doc || window.document;
var reporterView;
var dom = {};
// Jasmine Reporter Public Interface
self.logRunningSpecs = false;
self.reportRunnerStarting = function(runner) {
var specs = runner.specs() || [];
if (specs.length == 0) {
return;
}
createReporterDom(runner.env.versionString());
doc.body.appendChild(dom.reporter);
setExceptionHandling();
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
reporterView.addSpecs(specs, self.specFilter);
};
self.reportRunnerResults = function(runner) {
reporterView && reporterView.complete();
};
self.reportSuiteResults = function(suite) {
reporterView.suiteComplete(suite);
};
self.reportSpecStarting = function(spec) {
if (self.logRunningSpecs) {
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
self.reportSpecResults = function(spec) {
reporterView.specComplete(spec);
};
self.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
self.specFilter = function(spec) {
if (!focusedSpecName()) {
return true;
}
return spec.getFullName().indexOf(focusedSpecName()) === 0;
};
return self;
function focusedSpecName() {
var specName;
(function memoizeFocusedSpec() {
if (specName) {
return;
}
var paramMap = [];
var params = jasmine.HtmlReporter.parameters(doc);
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
specName = paramMap.spec;
})();
return specName;
}
function createReporterDom(version) {
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
dom.banner = self.createDom('div', { className: 'banner' },
self.createDom('span', { className: 'title' }, "Jasmine "),
self.createDom('span', { className: 'version' }, version)),
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
dom.alert = self.createDom('div', {className: 'alert'},
self.createDom('span', { className: 'exceptions' },
self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
dom.results = self.createDom('div', {className: 'results'},
dom.summary = self.createDom('div', { className: 'summary' }),
dom.details = self.createDom('div', { id: 'details' }))
);
}
function noTryCatch() {
return window.location.search.match(/catch=false/);
}
function searchWithCatch() {
var params = jasmine.HtmlReporter.parameters(window.document);
var removed = false;
var i = 0;
while (!removed && i < params.length) {
if (params[i].match(/catch=/)) {
params.splice(i, 1);
removed = true;
}
i++;
}
if (jasmine.CATCH_EXCEPTIONS) {
params.push("catch=false");
}
return params.join("&");
}
function setExceptionHandling() {
var chxCatch = document.getElementById('no_try_catch');
if (noTryCatch()) {
chxCatch.setAttribute('checked', true);
jasmine.CATCH_EXCEPTIONS = false;
}
chxCatch.onclick = function() {
window.location.search = searchWithCatch();
};
}
};
jasmine.HtmlReporter.parameters = function(doc) {
var paramStr = doc.location.search.substring(1);
var params = [];
if (paramStr.length > 0) {
params = paramStr.split('&');
}
return params;
}
jasmine.HtmlReporter.sectionLink = function(sectionName) {
var link = '?';
var params = [];
if (sectionName) {
params.push('spec=' + encodeURIComponent(sectionName));
}
if (!jasmine.CATCH_EXCEPTIONS) {
params.push("catch=false");
}
if (params.length > 0) {
link += params.join("&");
}
return link;
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
jasmine.HtmlReporter.ReporterView = function(dom) {
this.startedAt = new Date();
this.runningSpecCount = 0;
this.completeSpecCount = 0;
this.passedCount = 0;
this.failedCount = 0;
this.skippedCount = 0;
this.createResultsMenu = function() {
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
' | ',
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
this.summaryMenuItem.onclick = function() {
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
};
this.detailsMenuItem.onclick = function() {
showDetails();
};
};
this.addSpecs = function(specs, specFilter) {
this.totalSpecCount = specs.length;
this.views = {
specs: {},
suites: {}
};
for (var i = 0; i < specs.length; i++) {
var spec = specs[i];
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
if (specFilter(spec)) {
this.runningSpecCount++;
}
}
};
this.specComplete = function(spec) {
this.completeSpecCount++;
if (isUndefined(this.views.specs[spec.id])) {
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
}
var specView = this.views.specs[spec.id];
switch (specView.status()) {
case 'passed':
this.passedCount++;
break;
case 'failed':
this.failedCount++;
break;
case 'skipped':
this.skippedCount++;
break;
}
specView.refresh();
this.refresh();
};
this.suiteComplete = function(suite) {
var suiteView = this.views.suites[suite.id];
if (isUndefined(suiteView)) {
return;
}
suiteView.refresh();
};
this.refresh = function() {
if (isUndefined(this.resultsMenu)) {
this.createResultsMenu();
}
// currently running UI
if (isUndefined(this.runningAlert)) {
this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
dom.alert.appendChild(this.runningAlert);
}
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
// skipped specs UI
if (isUndefined(this.skippedAlert)) {
this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
}
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.skippedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.skippedAlert);
}
// passing specs UI
if (isUndefined(this.passedAlert)) {
this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
}
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
// failing specs UI
if (isUndefined(this.failedAlert)) {
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
}
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
if (this.failedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.failedAlert);
dom.alert.appendChild(this.resultsMenu);
}
// summary info
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
};
this.complete = function() {
dom.alert.removeChild(this.runningAlert);
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.failedCount === 0) {
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
} else {
showDetails();
}
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
};
return this;
function showDetails() {
if (dom.reporter.className.search(/showDetails/) === -1) {
dom.reporter.className += " showDetails";
}
}
function isUndefined(obj) {
return typeof obj === 'undefined';
}
function isDefined(obj) {
return !isUndefined(obj);
}
function specPluralizedFor(count) {
var str = count + " spec";
if (count > 1) {
str += "s"
}
return str;
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
this.spec = spec;
this.dom = dom;
this.views = views;
this.symbol = this.createDom('li', { className: 'pending' });
this.dom.symbolSummary.appendChild(this.symbol);
this.summary = this.createDom('div', { className: 'specSummary' },
this.createDom('a', {
className: 'description',
href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.description)
);
this.detail = this.createDom('div', { className: 'specDetail' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.getFullName())
);
};
jasmine.HtmlReporter.SpecView.prototype.status = function() {
return this.getSpecStatus(this.spec);
};
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
this.symbol.className = this.status();
switch (this.status()) {
case 'skipped':
break;
case 'passed':
this.appendSummaryToSuiteDiv();
break;
case 'failed':
this.appendSummaryToSuiteDiv();
this.appendFailureDetail();
break;
}
};
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
this.summary.className += ' ' + this.status();
this.appendToSummary(this.spec, this.summary);
};
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
this.detail.className += ' ' + this.status();
var resultItems = this.spec.results().getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
this.detail.appendChild(messagesDiv);
this.dom.details.appendChild(this.detail);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
this.suite = suite;
this.dom = dom;
this.views = views;
this.element = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
);
this.appendToSummary(this.suite, this.element);
};
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
return this.getSpecStatus(this.suite);
};
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
this.element.className += " " + this.status();
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
/* @deprecated Use jasmine.HtmlReporter instead
*/
jasmine.TrivialReporter = function(doc) {
this.document = doc || document;
this.suiteDivs = {};
this.logRunningSpecs = false;
};
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) { el.appendChild(child); }
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
var showPassed, showSkipped;
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
this.createDom('div', { className: 'banner' },
this.createDom('div', { className: 'logo' },
this.createDom('span', { className: 'title' }, "Jasmine"),
this.createDom('span', { className: 'version' }, runner.env.versionString())),
this.createDom('div', { className: 'options' },
"Show ",
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
)
),
this.runnerDiv = this.createDom('div', { className: 'runner running' },
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
);
this.document.body.appendChild(this.outerDiv);
var suites = runner.suites();
for (var i = 0; i < suites.length; i++) {
var suite = suites[i];
var suiteDiv = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
this.suiteDivs[suite.id] = suiteDiv;
var parentDiv = this.outerDiv;
if (suite.parentSuite) {
parentDiv = this.suiteDivs[suite.parentSuite.id];
}
parentDiv.appendChild(suiteDiv);
}
this.startedAt = new Date();
var self = this;
showPassed.onclick = function(evt) {
if (showPassed.checked) {
self.outerDiv.className += ' show-passed';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
}
};
showSkipped.onclick = function(evt) {
if (showSkipped.checked) {
self.outerDiv.className += ' show-skipped';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
}
};
};
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
var results = runner.results();
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
this.runnerDiv.setAttribute("class", className);
//do it twice for IE
this.runnerDiv.setAttribute("className", className);
var specs = runner.specs();
var specCount = 0;
for (var i = 0; i < specs.length; i++) {
if (this.specFilter(specs[i])) {
specCount++;
}
}
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
};
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
var results = suite.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.totalCount === 0) { // todo: change this to check results.skipped
status = 'skipped';
}
this.suiteDivs[suite.id].className += " " + status;
};
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
if (this.logRunningSpecs) {
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
var results = spec.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
var specDiv = this.createDom('div', { className: 'spec ' + status },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(spec.getFullName()),
title: spec.getFullName()
}, spec.description));
var resultItems = results.getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
specDiv.appendChild(messagesDiv);
}
this.suiteDivs[spec.suite.id].appendChild(specDiv);
};
jasmine.TrivialReporter.prototype.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
jasmine.TrivialReporter.prototype.getLocation = function() {
return this.document.location;
};
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
var paramMap = {};
var params = this.getLocation().search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
if (!paramMap.spec) {
return true;
}
return spec.getFullName().indexOf(paramMap.spec) === 0;
};

82
test/browser/jasmine.css Normal file
View File

@@ -0,0 +1,82 @@
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
#HTMLReporter a { text-decoration: none; }
#HTMLReporter a:hover { text-decoration: underline; }
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
#HTMLReporter .version { color: #aaaaaa; }
#HTMLReporter .banner { margin-top: 14px; }
#HTMLReporter .duration { color: #aaaaaa; float: right; }
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
#HTMLReporter .runningAlert { background-color: #666666; }
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
#HTMLReporter .passingAlert { background-color: #a6b779; }
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
#HTMLReporter .failingAlert { background-color: #cf867e; }
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
#HTMLReporter .results { margin-top: 14px; }
#HTMLReporter #details { display: none; }
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter.showDetails .summary { display: none; }
#HTMLReporter.showDetails #details { display: block; }
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter .summary { margin-top: 14px; }
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
#HTMLReporter .description + .suite { margin-top: 0; }
#HTMLReporter .suite { margin-top: 14px; }
#HTMLReporter .suite a { color: #333333; }
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
#HTMLReporter .resultMessage span.result { display: block; }
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
#TrivialReporter .runner.running { background-color: yellow; }
#TrivialReporter .options { text-align: right; font-size: .8em; }
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
#TrivialReporter .suite .suite { margin: 5px; }
#TrivialReporter .suite.passed { background-color: #dfd; }
#TrivialReporter .suite.failed { background-color: #fdd; }
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
#TrivialReporter .spec.skipped { background-color: #bbb; }
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
#TrivialReporter .passed { background-color: #cfc; display: none; }
#TrivialReporter .failed { background-color: #fbb; }
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
#TrivialReporter .resultMessage .mismatch { color: black; }
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }

2600
test/browser/jasmine.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,107 @@
var page = require('webpage').create();
var server = require('webserver').create();
var system = require('system');
var fs = require('fs');
var host, port = 8081;
var listening = server.listen(port, function (request, response) {
//console.log("Requested "+request.url);
var filename = ("test/" + request.url.slice(1)).replace(/[\\\/]/g, fs.separator);
if (!fs.exists(filename) || !fs.isFile(filename)) {
response.statusCode = 404;
response.write("<html><head></head><body><h1>File Not Found</h1><h2>File:"+filename+"</h2></body></html>");
response.close();
return;
}
// we set the headers here
response.statusCode = 200;
response.headers = {"Cache": "no-cache", "Content-Type": "text/html"};
response.write(fs.read(filename));
response.close();
});
if (!listening) {
console.log("could not create web server listening on port " + port);
phantom.exit();
}
/**
* Wait until the test condition is true or a timeout occurs. Useful for waiting
* on a server response or for a ui change (fadeIn, etc.) to occur.
*
* @param testFx javascript condition that evaluates to a boolean,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param onReady what to do when testFx condition is fulfilled,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
*/
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 10001, //< Default Max Timeout is 10s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if(!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
//console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 100); //< repeat check every 100ms
};
if (system.args.length != 2 && system.args[1] != "--no-tests") {
page.open("http://localhost:8081/browser/test-runner-main.htm", function (status) {
if (status !== "success") {
console.log("Unable to access network");
phantom.exit();
} else {
waitFor(function(){
return page.evaluate(function(){
return document.body.querySelector('.symbolSummary .pending') === null &&
document.body.querySelector('.results') !== null;
});
}, function(){
page.onConsoleMessage = function (msg) {
console.log(msg);
};
var exitCode = page.evaluate(function(){
console.log('');
console.log(document.body.querySelector('.description').innerText);
var list = document.body.querySelectorAll('.results > #details > .specDetail.failed');
if (list && list.length > 0) {
console.log('');
console.log(list.length + ' test(s) FAILED:');
for (i = 0; i < list.length; ++i) {
var el = list[i],
desc = el.querySelector('.description'),
msg = el.querySelector('.resultMessage.fail');
console.log('');
console.log(desc.innerText);
console.log(msg.innerText);
console.log('');
}
return 1;
} else {
console.log(document.body.querySelector('.alert > .passingAlert.bar').innerText);
return 0;
}
});
phantom.exit(exitCode);
});
}
});
}

View File

@@ -0,0 +1,42 @@
describe("less.js", function() {
var links = document.getElementsByTagName('link'),
typePattern = /^text\/(x-)?less$/,
testSheet = function(sheet) {
it(sheet.id + " should match the expected output", function() {
var lessOutputId = sheet.id.replace("original-", ""),
expectedOutputId = "expected-" + lessOutputId,
lessOutput = document.getElementById(lessOutputId).innerText,
expectedOutputHref = document.getElementById(expectedOutputId).href,
expectedOutput = loadFile(expectedOutputHref);
waitsFor(function() {
return expectedOutput.loaded;
}, "failed to load expected outout", 10000);
runs(function() {
// use sheet to do testing
expect(lessOutput).toEqual(expectedOutput.text);
});
});
};
for (var i = 0; i < links.length; i++) {
if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
(links[i].type.match(typePattern)))) {
testSheet(links[i]);
}
}
});
var loadFile = function(href) {
var request = new XMLHttpRequest(),
response = { loaded: false, text: ""};
request.open('GET', href, true);
request.onload = function(e) {
response.text = request.response;
response.loaded = true;
}
request.send();
return response;
};

55
test/browser/template.htm Normal file
View File

@@ -0,0 +1,55 @@
<script type="text/javascript">
/*if not async then phantomjs fails to run the webserver and the test concurrently*/
less = { async: true,
functions: {
add: function (a, b) {
return new(less.tree.Dimension)(a.value + b.value);
},
increment: function (a) {
return new(less.tree.Dimension)(a.value + 1);
},
_color: function (str) {
if (str.value === "evil red") { return new(less.tree.Color)("600") }
}
}
};
</script>
<script src="http://localhost:8081/browser/less.js" type="text/javascript"></script>
<script src="http://localhost:8081/browser/jasmine.js" type="text/javascript"></script>
<script src="http://localhost:8081/browser/jasmine-html.js" type="text/javascript"></script>
<script src="http://localhost:8081/browser/runner-{runner-name}.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="http://localhost:8081/browser/jasmine.css"></link>
<script type="text/javascript">
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
setTimeout(function() {
jasmineEnv.execute();
}, 3000);
}
})();
</script>
</head>
<body>
</body>
</html>

View File

@@ -1,11 +1,9 @@
.comma-delimited {
background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
text-shadow: -1px -1px 1px #ff0000, 6px 5px 5px #ffff00;
-moz-box-shadow: 0pt 0pt 2px rgba(255, 255, 255, 0.4) inset, 0pt 4px 6px rgba(255, 255, 255, 0.4) inset;
}
@font-face {
font-family: Headline;
src: local(Futura-Medium), url(fonts.svg#MyGeometricModern) format("svg");
unicode-range: U+??????, U+0???, U+0-7F, U+A5;
}
.other {
@@ -99,17 +97,17 @@ p::before {
}
@namespace foo url(http://www.example.com);
foo | h1 {
color: #0000ff;
color: blue;
}
foo | * {
color: #ffff00;
color: yellow;
}
| h1 {
color: #ff0000;
color: red;
}
* | h1 {
color: #008000;
color: green;
}
h1 {
color: #008000;
color: green;
}

View File

@@ -33,7 +33,6 @@ div#id {
}
@font-face {
font-family: 'Garamond Pro';
src: url("/fonts/garamond-pro.ttf");
}
a:hover,
a:link {
@@ -55,7 +54,6 @@ p + h1 {
font: 100%/16px Arial;
margin: 1px 0;
padding: 0 auto;
background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
}
#more-shorthands {
margin: 0;
@@ -68,7 +66,6 @@ p + h1 {
display: -moz-inline-stack;
width: .1em;
background-color: #009998;
background-image: url(images/image.jpg);
background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), to(#0000ff));
margin: ;
filter: alpha(opacity=100);
@@ -81,17 +78,6 @@ p + h1 {
width: 100%!important;
height: 20px ! important;
}
#data-uri {
background: url(data:image/png;charset=utf-8;base64,
kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
}
#svg-data-uri {
background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
}
@font-face {
font-family: font-a;
}

27
test/css/urls.css Normal file
View File

@@ -0,0 +1,27 @@
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium), url(fonts.svg#MyGeometricModern) format("svg");
}
#shorthands {
background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
}
#misc {
background-image: url(images/image.jpg);
}
#data-uri {
background: url(data:image/png;charset=utf-8;base64,
kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
}
#svg-data-uri {
background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
}
.comma-delimited {
background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
}
.values {
url: url('Trebuchet');
}

View File

@@ -13,7 +13,6 @@
.values {
font-family: 'Trebuchet', 'Trebuchet', 'Trebuchet';
color: #888888 !important;
url: url('Trebuchet');
multi: something 'A', B, C, 'Trebuchet';
}
.variable-names {

View File

@@ -26,7 +26,7 @@ sys.puts("\n" + stylize("LESS", 'underline') + "\n");
runTestSet();
runTestSet(null, "errors/", function(name, err, compiledLess, doReplacements) {
fs.readFile(path.join('test/less/', name) + '.txt', 'utf-8', function (e, expectedErr) {
fs.readFile(path.join('test/less/', name) + '.txt', 'utf8', function (e, expectedErr) {
sys.print("- " + name + ": ");
expectedErr = doReplacements(expectedErr, 'test/less/errors/');
if (!err) {
@@ -93,7 +93,7 @@ function runTestSet(options, foldername, verifyFunction, nameModifier, doReplace
}
var css_name = name;
if(nameModifier) css_name=nameModifier(name);
fs.readFile(path.join('test/css', css_name) + '.css', 'utf-8', function (e, css) {
fs.readFile(path.join('test/css', css_name) + '.css', 'utf8', function (e, css) {
sys.print("- " + css_name + ": ")
css = css && doReplacements(css, 'test/less/' + foldername);
@@ -158,7 +158,7 @@ function endTest() {
function toCSS(options, path, callback) {
var tree, css;
options = options || {};
fs.readFile(path, 'utf-8', function (e, str) {
fs.readFile(path, 'utf8', function (e, str) {
if (e) { return callback(e) }
options.paths = [require('path').dirname(path)];

View File

@@ -1,13 +1,10 @@
.comma-delimited {
background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
text-shadow: -1px -1px 1px red, 6px 5px 5px yellow;
-moz-box-shadow: 0pt 0pt 2px rgba(255, 255, 255, 0.4) inset,
0pt 4px 6px rgba(255, 255, 255, 0.4) inset;
}
@font-face {
font-family: Headline;
src: local(Futura-Medium),
url(fonts.svg#MyGeometricModern) format("svg");
unicode-range: U+??????, U+0???, U+0-7F, U+A5;
}
.other {
@@ -108,8 +105,9 @@ p::before {
font-size: 10px;
}
@namespace foo url(http://www.example.com);
foo|h1 { color: blue }
foo|* { color: yellow }
|h1 { color: red }
*|h1 { color: green }
h1 { color: green }
foo|h1 { color: blue; }
foo|* { color: yellow; }
|h1 { color: red; }
*|h1 { color: green; }
h1 { color: green; }

View File

@@ -36,7 +36,6 @@ div#id {
@font-face {
font-family: 'Garamond Pro';
src: url("/fonts/garamond-pro.ttf");
}
a:hover, a:link {
@@ -61,7 +60,6 @@ p + h1 {
font: 100%/16px Arial;
margin: 1px 0;
padding: 0 auto;
background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
}
#more-shorthands {
@@ -76,7 +74,6 @@ p + h1 {
display: -moz-inline-stack;
width: .1em;
background-color: #009998;
background-image: url(images/image.jpg);
background: -webkit-gradient(linear, left top, left bottom, from(red), to(blue));
margin: ;
.nested-multiple {
@@ -91,19 +88,6 @@ p + h1 {
height: 20px ! important;
}
#data-uri {
background: url(data:image/png;charset=utf-8;base64,
kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
}
#svg-data-uri {
background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
}
.def-font(@name) {
@font-face {
font-family: @name

31
test/less/urls.less Normal file
View File

@@ -0,0 +1,31 @@
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium),
url(fonts.svg#MyGeometricModern) format("svg");
}
#shorthands {
background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
}
#misc {
background-image: url(images/image.jpg);
}
#data-uri {
background: url(data:image/png;charset=utf-8;base64,
kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
}
#svg-data-uri {
background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
}
.comma-delimited {
background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
}
.values {
@a: 'Trebuchet';
url: url(@a);
}

View File

@@ -35,7 +35,6 @@
@multi: 'A', B, C;
font-family: @a, @a, @a;
color: @c !important;
url: url(@a);
multi: something @multi, @a;
}