diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2012-05-03 13:01:35 +0200 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2012-05-03 13:01:35 +0200 |
commit | d9022f63880ce039446fba8364f68e656b7bf4cb (patch) | |
tree | 16b40fbf17bf7c9ee6f4ead25b16dd192378050a /tests | |
parent | 27cf83d177256813e2e802241085fce5dd0f3fb9 (diff) |
Update to MediaWiki 1.19.0
Diffstat (limited to 'tests')
217 files changed, 15042 insertions, 2702 deletions
diff --git a/tests/TestsAutoLoader.php b/tests/TestsAutoLoader.php index cccf7bf2..41bddfcd 100644 --- a/tests/TestsAutoLoader.php +++ b/tests/TestsAutoLoader.php @@ -21,9 +21,6 @@ $wgAutoloadClasses += array( 'RandomImageGenerator' => "$testFolder/phpunit/includes/api/RandomImageGenerator.php", 'UserWrapper' => "$testFolder/phpunit/includes/api/ApiTestCase.php", - //Parser - 'ParserTestFileIterator' => "$testFolder/phpunit/includes/parser/NewParserHelpers.php", - //Selenium 'SeleniumTestConstants' => "$testFolder/selenium/SeleniumTestConstants.php", diff --git a/tests/jasmine/.htaccess b/tests/jasmine/.htaccess new file mode 100644 index 00000000..605d2f4c --- /dev/null +++ b/tests/jasmine/.htaccess @@ -0,0 +1 @@ +Allow from all diff --git a/tests/jasmine/SpecRunner.html b/tests/jasmine/SpecRunner.html new file mode 100644 index 00000000..6af9b0c3 --- /dev/null +++ b/tests/jasmine/SpecRunner.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> + <title>Jasmine Test Runner</title> + <link rel="stylesheet" type="text/css" href="lib/jasmine-1.0.1/jasmine.css"> + <script type="text/javascript" src="lib/jasmine-1.0.1/jasmine.js"></script> + <script type="text/javascript" src="lib/jasmine-1.0.1/jasmine-html.js"></script> + + <!-- include source files here... --> + <script type="text/javascript" src="../../load.php?debug=true&lang=en&modules=jquery%7Cmediawiki&only=scripts&skin=vector"></script> + + <script type="text/javascript" src="../../resources/mediawiki/mediawiki.js"></script> + + <script type="text/javascript" src="../../resources/mediawiki.language/mediawiki.language.js"></script> + <script type="text/javascript" src="../../resources/mediawiki/mediawiki.jqueryMsg.js"></script> + <script type="text/javascript" src="../../resources/mediawiki/mediawiki.Uri.js"></script> +<!-- + <script type="text/javascript" src="../../resources/mediawiki/mediawiki.api.js"></script> + <script type="text/javascript" src="../../resources/mediawiki/mediawiki.api.edit.js"></script> +--> + + <!-- insert test data files here --> + <script type="text/javascript" src="spec/mediawiki.jqueryMsg.spec.data.js"></script> + + <!-- include spec files here... --> + <script type="text/javascript" src="spec/mediawiki.Uri.spec.js"></script> + <!-- + <script type="text/javascript" src="spec/mw.Api.spec.js"></script> + <script type="text/javascript" src="spec/mw.Api.edit.spec.js"></script> + --> + <script type="text/javascript" src="spec/mediawiki.jqueryMsg.spec.js"></script> + +</head> +<body> +<script type="text/javascript"> + jasmine.getEnv().addReporter( new jasmine.TrivialReporter() ); + jasmine.getEnv().execute(); +</script> + +</body> +</html> diff --git a/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE b/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE new file mode 100644 index 00000000..1eb9b49e --- /dev/null +++ b/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008-2010 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js b/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js new file mode 100644 index 00000000..81402b9c --- /dev/null +++ b/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js @@ -0,0 +1,188 @@ +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', { className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('a', { href: 'http://pivotal.github.com/jasmine/', target: "_blank" }, "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; +}; diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine.css b/tests/jasmine/lib/jasmine-1.0.1/jasmine.css new file mode 100644 index 00000000..6583fe7c --- /dev/null +++ b/tests/jasmine/lib/jasmine-1.0.1/jasmine.css @@ -0,0 +1,166 @@ +body { + font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; +} + + +.jasmine_reporter a:visited, .jasmine_reporter a { + color: #303; +} + +.jasmine_reporter a:hover, .jasmine_reporter a:active { + color: blue; +} + +.run_spec { + float:right; + padding-right: 5px; + font-size: .8em; + text-decoration: none; +} + +.jasmine_reporter { + margin: 0 5px; +} + +.banner { + color: #303; + background-color: #fef; + padding: 5px; +} + +.logo { + float: left; + font-size: 1.1em; + padding-left: 5px; +} + +.logo .version { + font-size: .6em; + padding-left: 1em; +} + +.runner.running { + background-color: yellow; +} + + +.options { + text-align: right; + font-size: .8em; +} + + + + +.suite { + border: 1px outset gray; + margin: 5px 0; + padding-left: 1em; +} + +.suite .suite { + margin: 5px; +} + +.suite.passed { + background-color: #dfd; +} + +.suite.failed { + background-color: #fdd; +} + +.spec { + margin: 5px; + padding-left: 1em; + clear: both; +} + +.spec.failed, .spec.passed, .spec.skipped { + padding-bottom: 5px; + border: 1px solid gray; +} + +.spec.failed { + background-color: #fbb; + border-color: red; +} + +.spec.passed { + background-color: #bfb; + border-color: green; +} + +.spec.skipped { + background-color: #bbb; +} + +.messages { + border-left: 1px dashed gray; + padding-left: 1em; + padding-right: 1em; +} + +.passed { + background-color: #cfc; + display: none; +} + +.failed { + background-color: #fbb; +} + +.skipped { + color: #777; + background-color: #eee; + display: none; +} + + +/*.resultMessage {*/ + /*white-space: pre;*/ +/*}*/ + +.resultMessage span.result { + display: block; + line-height: 2em; + color: black; +} + +.resultMessage .mismatch { + color: black; +} + +.stackTrace { + white-space: pre; + font-size: .8em; + margin-left: 10px; + max-height: 5em; + overflow: auto; + border: 1px inset red; + padding: 1em; + background: #eef; +} + +.finished-at { + padding-left: 1em; + font-size: .6em; +} + +.show-passed .passed, +.show-skipped .skipped { + display: block; +} + + +#jasmine_content { + position:fixed; + right: 100%; +} + +.runner { + border: 1px solid gray; + display: block; + margin: 5px 0; + padding: 2px 0 2px 10px; +} diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine.js b/tests/jasmine/lib/jasmine-1.0.1/jasmine.js new file mode 100644 index 00000000..964f99ed --- /dev/null +++ b/tests/jasmine/lib/jasmine-1.0.1/jasmine.js @@ -0,0 +1,2421 @@ +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; + +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +jasmine.getGlobal = function() { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function(base, name) { + var original = base[name]; + if (original.apply) { + return function() { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function(values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function() { + var text = ""; + for(var i = 0; i < this.values.length; i++) { + if (i > 0) text += " "; + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function(params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + + this.message = this.passed_ ? 'Passed.' : params.message; + this.trace = this.passed_ ? '' : new Error(this.message); +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function() { + return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function(value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function(value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function(obj) { + return obj['nodeType'] > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length == 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to <code>jasmine.log</code> in production code. + */ +jasmine.log = function() { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @returns a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; + +/** + * Creates a <em>disabled</em> Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + try { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + } catch(e) { + } + try { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + } catch(e) { + } + try { + return new ActiveXObject("Msxml2.XMLHTTP"); + } catch(e) { + } + try { + return new ActiveXObject("Microsoft.XMLHTTP"); + } catch(e) { + } + throw new Error("This browser does not support XMLHttpRequest."); +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function(childClass, parentClass) { + /** + * @private + */ + var subclass = function() { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass; +}; + +jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); +}; + +jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; + +jasmine.util.extend = function(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function() { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function() { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { + if (jasmine.version_) { + var version = this.version(); + return version.major + "." + version.minor + "." + version.build + " revision " + version.revision; + } else { + return "version unknown"; + } +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function(reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function() { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function(description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch(e) { + declarationError = e; + } + + this.currentSuite = parentSuite; + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function(afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { + return { + execute: function() { + } + }; +}; + +jasmine.Env.prototype.it = function(description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function(desc, func) { + return { + id: this.nextSpecId(), + runs: function() { + } + }; +}; + +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function(obj, keyName) { + return obj != null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') continue; + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length == 0 && mismatchValues.length == 0); +}; + +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) return result; + } + + if (a === b) return true; + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a instanceof jasmine.Matchers.Any) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.Any) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function(haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) return true; + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function(equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function() { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function(str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function(env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function(onComplete) { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function() { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function() { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id: suiteOrSpec.id, + name: suiteOrSpec.description, + type: isSuite ? 'suite' : 'spec', + children: [] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function() { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { + this.results_[spec.id] = { + messages: spec.results().getItems(), + result: spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function(str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed: resultMessage.passed ? resultMessage.passed() : true, + type: resultMessage.type, + message: resultMessage.message, + trace: { + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result : result.result, + messages : summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function(env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function(str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function(result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') continue; + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { + return function() { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) return result; + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) message += ","; + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName: matcherName, + passed: result, + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual: this.actual, + message: message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function(expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function(expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function(expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toNotEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function(expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function(expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function(expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function() { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function() { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function() { + return (this.actual === null); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function() { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function() { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function() { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function() { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function() { + if (this.actual.callCount == 0) { + // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] + return [ + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was." + ]; + } else { + return [ + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) + ]; + } + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ] + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function(expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toNotContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function(expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function(expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} expected + */ +jasmine.Matchers.prototype.toThrow = function(expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function() { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : " an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.matches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.toString = function() { + return '<jasmine.any(' + this.expectedClass + ')>'; +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function() { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function(reporter) { + this.subReporters_.push(reporter); +}; + +(function() { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function(functionName) { + return function() { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function() { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function(values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function() { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function(result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if <b>everything</b> below passed + */ +jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function() { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function(value) { + if (this.ppNestLevel_ > 40) { + throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); + } + + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar('<global>'); + } else if (value instanceof jasmine.Matchers.Any) { + this.emitScalar(value.toString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>'); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (property == '__Jasmine_been_here_before__') continue; + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) != null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function() { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append('<getter>'); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function(value) { + this.string += value; +}; +jasmine.Queue = function(env) { + this.env = env; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function(block) { + this.blocks.unshift(block); +}; + +jasmine.Queue.prototype.add = function(block) { + this.blocks.push(block); +}; + +jasmine.Queue.prototype.insertNext = function(block) { + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function(onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function() { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function() { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !this.abort) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function() { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function() { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function(env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function() { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0,0,beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0,0,afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function() { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function(suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function(block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function() { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function() { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function(env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function() { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function() { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to <code>jasmine.log</code> in production code. + */ +jasmine.Spec.prototype.log = function() { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function(result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function(actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function(timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception' + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function() { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function() { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function() { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function(onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function(doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this)); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function(onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); + } +}; + +jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function(env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function(onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function() { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function(suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function() { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function() { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function(onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function(env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function(onComplete) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name: 'timeout', + message: message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function() { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function() { + this.reset(); + + var self = this; + self.setTimeout = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function() { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis: this.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer: new jasmine.FakeTimer(), + + reset: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick: function(millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange: function(oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock: function() { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock: function() { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real: { + setTimeout: jasmine.getGlobal().setTimeout, + clearTimeout: jasmine.getGlobal().clearTimeout, + setInterval: jasmine.getGlobal().setInterval, + clearInterval: jasmine.getGlobal().clearInterval + }, + + assertInstalled: function() { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled: function() { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed: null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function(funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function(funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + + +jasmine.version_= { + "major": 1, + "minor": 0, + "build": 1, + "revision": 1286311016 +}; diff --git a/tests/jasmine/spec/mediawiki.Uri.spec.js b/tests/jasmine/spec/mediawiki.Uri.spec.js new file mode 100644 index 00000000..721ccb38 --- /dev/null +++ b/tests/jasmine/spec/mediawiki.Uri.spec.js @@ -0,0 +1,307 @@ +( function() { + + // ensure we have a generic URI parser if not running in a browser + if ( !mw.Uri ) { + mw.Uri = mw.UriRelative( 'http://example.com/' ); + } + + describe( "mw.Uri", function() { + + describe( "should work well in loose and strict mode", function() { + + function basicTests( strict ) { + + describe( "should parse a simple HTTP URI correctly", function() { + + var uriString = 'http://www.ietf.org/rfc/rfc2396.txt'; + var uri; + if ( strict ) { + uri = new mw.Uri( uriString, strict ); + } else { + uri = new mw.Uri( uriString ); + } + + it( "should have basic object properties", function() { + expect( uri.protocol ).toEqual( 'http' ); + expect( uri.host ).toEqual( 'www.ietf.org' ); + expect( uri.port ).not.toBeDefined(); + expect( uri.path ).toEqual( '/rfc/rfc2396.txt' ); + expect( uri.query ).toEqual( {} ); + expect( uri.fragment ).not.toBeDefined(); + } ); + + describe( "should construct composite components of URI on request", function() { + it( "should have empty userinfo", function() { + expect( uri.getUserInfo() ).toEqual( '' ); + } ); + + it( "should have authority equal to host", function() { + expect( uri.getAuthority() ).toEqual( 'www.ietf.org' ); + } ); + + it( "should have hostport equal to host", function() { + expect( uri.getHostPort() ).toEqual( 'www.ietf.org' ); + } ); + + it( "should have empty string as query string", function() { + expect( uri.getQueryString() ).toEqual( '' ); + } ); + + it( "should have path as relative path", function() { + expect( uri.getRelativePath() ).toEqual( '/rfc/rfc2396.txt' ); + } ); + + it( "should return a uri string equivalent to original", function() { + expect( uri.toString() ).toEqual( uriString ); + } ); + } ); + } ); + } + + describe( "should work in loose mode", function() { + basicTests( false ); + } ); + + describe( "should work in strict mode", function() { + basicTests( true ); + } ); + + } ); + + it( "should parse a simple ftp URI correctly with user and password", function() { + var uri = new mw.Uri( 'ftp://usr:pwd@192.0.2.16/' ); + expect( uri.protocol ).toEqual( 'ftp' ); + expect( uri.user ).toEqual( 'usr' ); + expect( uri.password ).toEqual( 'pwd' ); + expect( uri.host ).toEqual( '192.0.2.16' ); + expect( uri.port ).not.toBeDefined(); + expect( uri.path ).toEqual( '/' ); + expect( uri.query ).toEqual( {} ); + expect( uri.fragment ).not.toBeDefined(); + } ); + + it( "should parse a simple querystring", function() { + var uri = new mw.Uri( 'http://www.google.com/?q=uri' ); + expect( uri.protocol ).toEqual( 'http' ); + expect( uri.host ).toEqual( 'www.google.com' ); + expect( uri.port ).not.toBeDefined(); + expect( uri.path ).toEqual( '/' ); + expect( uri.query ).toBeDefined(); + expect( uri.query ).toEqual( { q: 'uri' } ); + expect( uri.fragment ).not.toBeDefined(); + expect( uri.getQueryString() ).toEqual( 'q=uri' ); + } ); + + describe( "should handle multiple value query args (overrideKeys on)", function() { + var uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { overrideKeys: true } ); + it ( "should parse with multiple values", function() { + expect( uri.query.m ).toEqual( 'bar' ); + expect( uri.query.n ).toEqual( '1' ); + } ); + it ( "should accept multiple values", function() { + uri.query.n = [ "x", "y", "z" ]; + expect( uri.toString() ).toContain( 'm=bar' ); + expect( uri.toString() ).toContain( 'n=x&n=y&n=z' ); + expect( uri.toString().length ).toEqual( 'http://www.example.com/dir/?m=bar&n=x&n=y&n=z'.length ); + } ); + } ); + + describe( "should handle multiple value query args (overrideKeys off)", function() { + var uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { overrideKeys: false } ); + it ( "should parse with multiple values", function() { + expect( uri.query.m.length ).toEqual( 2 ); + expect( uri.query.m[0] ).toEqual( 'foo' ); + expect( uri.query.m[1] ).toEqual( 'bar' ); + expect( uri.query.n ).toEqual( '1' ); + } ); + it ( "should accept multiple values", function() { + uri.query.n = [ "x", "y", "z" ]; + expect( uri.toString() ).toContain( 'm=foo&m=bar' ); + expect( uri.toString() ).toContain( 'n=x&n=y&n=z' ); + expect( uri.toString().length ).toEqual( 'http://www.example.com/dir/?m=foo&m=bar&n=x&n=y&n=z'.length ); + } ); + it ( "should be okay with removing values", function() { + uri.query.m.splice( 0, 1 ); + delete uri.query.n; + expect( uri.toString() ).toEqual( 'http://www.example.com/dir/?m=bar' ); + uri.query.m.splice( 0, 1 ); + expect( uri.toString() ).toEqual( 'http://www.example.com/dir/' ); + } ); + } ); + + describe( "should deal with an all-dressed URI with everything", function() { + var uri = new mw.Uri( 'http://auth@www.example.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#top' ); + + it( "should have basic object properties", function() { + expect( uri.protocol ).toEqual( 'http' ); + expect( uri.user ).toEqual( 'auth' ); + expect( uri.password ).not.toBeDefined(); + expect( uri.host ).toEqual( 'www.example.com' ); + expect( uri.port ).toEqual( '81' ); + expect( uri.path ).toEqual( '/dir/dir.2/index.htm' ); + expect( uri.query ).toEqual( { q1: '0', test1: null, test2: 'value (escaped)' } ); + expect( uri.fragment ).toEqual( 'top' ); + } ); + + describe( "should construct composite components of URI on request", function() { + it( "should have userinfo", function() { + expect( uri.getUserInfo() ).toEqual( 'auth' ); + } ); + + it( "should have authority equal to auth@hostport", function() { + expect( uri.getAuthority() ).toEqual( 'auth@www.example.com:81' ); + } ); + + it( "should have hostport equal to host:port", function() { + expect( uri.getHostPort() ).toEqual( 'www.example.com:81' ); + } ); + + it( "should have query string which contains all components", function() { + var queryString = uri.getQueryString(); + expect( queryString ).toContain( 'q1=0' ); + expect( queryString ).toContain( 'test1' ); + expect( queryString ).not.toContain( 'test1=' ); + expect( queryString ).toContain( 'test2=value+%28escaped%29' ); + } ); + + it( "should have path as relative path", function() { + expect( uri.getRelativePath() ).toContain( uri.path ); + expect( uri.getRelativePath() ).toContain( uri.getQueryString() ); + expect( uri.getRelativePath() ).toContain( uri.fragment ); + } ); + + } ); + } ); + + describe( "should be able to clone itself", function() { + var original = new mw.Uri( 'http://en.wiki.local/w/api.php?action=query&foo=bar' ); + var clone = original.clone(); + + it( "should make clones equivalent", function() { + expect( original ).toEqual( clone ); + expect( original.toString() ).toEqual( clone.toString() ); + } ); + + it( "should be able to manipulate clones independently", function() { + // but they are still different objects + expect( original ).not.toBe( clone ); + // and can diverge + clone.host = 'fr.wiki.local'; + expect( original.host ).not.toEqual( clone.host ); + expect( original.toString() ).not.toEqual( clone.toString() ); + } ); + } ); + + describe( "should be able to construct URL from object", function() { + it ( "should construct given basic arguments", function() { + var uri = new mw.Uri( { protocol: 'http', host: 'www.foo.local', path: '/this' } ); + expect( uri.toString() ).toEqual( 'http://www.foo.local/this' ); + } ); + + it ( "should construct given more complex arguments", function() { + var uri = new mw.Uri( { + protocol: 'http', + host: 'www.foo.local', + path: '/this', + query: { hi: 'there' }, + fragment: 'blah' + } ); + expect( uri.toString() ).toEqual( 'http://www.foo.local/this?hi=there#blah' ); + } ); + + it ( "should fail to construct without required properties", function() { + expect( function() { + var uri = new mw.Uri( { protocol: 'http', host: 'www.foo.local' } ); + } ).toThrow( "Bad constructor arguments" ); + } ); + } ); + + describe( "should be able to manipulate properties", function() { + var uri; + + beforeEach( function() { + uri = new mw.Uri( 'http://en.wiki.local/w/api.php' ); + } ); + + it( "can add a fragment", function() { + uri.fragment = 'frag'; + expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php#frag' ); + } ); + + it( "can change host and port", function() { + uri.host = 'fr.wiki.local'; + uri.port = '8080'; + expect( uri.toString() ).toEqual( 'http://fr.wiki.local:8080/w/api.php' ); + } ); + + it ( "can add query arguments", function() { + uri.query.foo = 'bar'; + expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' ); + } ); + + it ( "can extend query arguments", function() { + uri.query.foo = 'bar'; + expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' ); + uri.extend( { foo: 'quux', pif: 'paf' } ); + expect( uri.toString() ).toContain( 'foo=quux' ); + expect( uri.toString() ).not.toContain( 'foo=bar' ); + expect( uri.toString() ).toContain( 'pif=paf' ); + } ); + + it ( "can remove query arguments", function() { + uri.query.foo = 'bar'; + expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' ); + delete( uri.query.foo ); + expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php' ); + } ); + + } ); + + describe( "should handle protocol-relative URLs", function() { + + it ( "should create protocol-relative URLs with same protocol as document", function() { + var uriRel = mw.UriRelative( 'glork://en.wiki.local/foo.php' ); + var uri = new uriRel( '//en.wiki.local/w/api.php' ); + expect( uri.protocol ).toEqual( 'glork' ); + } ); + + } ); + + it( "should throw error on no arguments to constructor", function() { + expect( function() { + var uri = new mw.Uri(); + } ).toThrow( "Bad constructor arguments" ); + } ); + + it( "should throw error on empty string as argument to constructor", function() { + expect( function() { + var uri = new mw.Uri( '' ); + } ).toThrow( "Bad constructor arguments" ); + } ); + + it( "should throw error on non-URI as argument to constructor", function() { + expect( function() { + var uri = new mw.Uri( 'glaswegian penguins' ); + } ).toThrow( "Bad constructor arguments" ); + } ); + + it( "should throw error on improper URI as argument to constructor", function() { + expect( function() { + var uri = new mw.Uri( 'http:/foo.com' ); + } ).toThrow( "Bad constructor arguments" ); + } ); + + it( "should throw error on URI without protocol or // in strict mode", function() { + expect( function() { + var uri = new mw.Uri( 'foo.com/bar/baz', true ); + } ).toThrow( "Bad constructor arguments" ); + } ); + + it( "should normalize URI without protocol or // in loose mode", function() { + var uri = new mw.Uri( 'foo.com/bar/baz', false ); + expect( uri.toString() ).toEqual( 'http://foo.com/bar/baz' ); + } ); + + } ); + +} )(); diff --git a/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js new file mode 100644 index 00000000..a867f72c --- /dev/null +++ b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js @@ -0,0 +1,488 @@ +// This file stores the results from the PHP parser for certain messages and arguments, +// so we can test the equivalent Javascript libraries. +// Last generated with makeLanguageSpec.php at 2011-01-28T02:04:09+00:00 + +mediaWiki.messages.set( { + "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}", + "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}", + "fr_undelete_short": "Restaurer $1 modification{{PLURAL:$1||s}}", + "fr_category-subcat-count": "Cette cat\u00e9gorie comprend {{PLURAL:$2|la sous-cat\u00e9gorie|$2 sous-cat\u00e9gories, dont {{PLURAL:$1|celle|les $1}}}} ci-dessous.", + "ar_undelete_short": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 {{PLURAL:$1|\u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f|\u062a\u0639\u062f\u064a\u0644\u064a\u0646|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u062a|$1 \u062a\u0639\u062f\u064a\u0644|$1 \u062a\u0639\u062f\u064a\u0644\u0627}}", + "ar_category-subcat-count": "{{PLURAL:$2|\u0644\u0627 \u062a\u0635\u0627\u0646\u064a\u0641 \u0641\u0631\u0639\u064a\u0629 \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 {{PLURAL:$1||\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a|\u0647\u0630\u064a\u0646 \u0627\u0644\u062a\u0635\u0646\u064a\u0641\u064a\u0646 \u0627\u0644\u0641\u0631\u0639\u064a\u064a\u0646|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641\u0627 \u0641\u0631\u0639\u064a\u0627|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641 \u0641\u0631\u0639\u064a}}\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a $2.}}", + "jp_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}", + "jp_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}", + "zh_undelete_short": "\u6062\u590d\u88ab\u5220\u9664\u7684$1\u9879\u4fee\u8ba2", + "zh_category-subcat-count": "{{PLURAL:$2|\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002|\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u5217$1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u6709$2\u4e2a\u5b50\u5206\u7c7b\u3002}}" +} ); +var jasmineMsgSpec = [ + { + "name": "en undelete_short 0", + "key": "en_undelete_short", + "args": [ + 0 + ], + "result": "Undelete 0 edits", + "lang": "en" + }, + { + "name": "en undelete_short 1", + "key": "en_undelete_short", + "args": [ + 1 + ], + "result": "Undelete one edit", + "lang": "en" + }, + { + "name": "en undelete_short 2", + "key": "en_undelete_short", + "args": [ + 2 + ], + "result": "Undelete 2 edits", + "lang": "en" + }, + { + "name": "en undelete_short 5", + "key": "en_undelete_short", + "args": [ + 5 + ], + "result": "Undelete 5 edits", + "lang": "en" + }, + { + "name": "en undelete_short 21", + "key": "en_undelete_short", + "args": [ + 21 + ], + "result": "Undelete 21 edits", + "lang": "en" + }, + { + "name": "en undelete_short 101", + "key": "en_undelete_short", + "args": [ + 101 + ], + "result": "Undelete 101 edits", + "lang": "en" + }, + { + "name": "en category-subcat-count 0,10", + "key": "en_category-subcat-count", + "args": [ + 0, + 10 + ], + "result": "This category has the following 0 subcategories, out of 10 total.", + "lang": "en" + }, + { + "name": "en category-subcat-count 1,1", + "key": "en_category-subcat-count", + "args": [ + 1, + 1 + ], + "result": "This category has only the following subcategory.", + "lang": "en" + }, + { + "name": "en category-subcat-count 1,2", + "key": "en_category-subcat-count", + "args": [ + 1, + 2 + ], + "result": "This category has the following subcategory, out of 2 total.", + "lang": "en" + }, + { + "name": "en category-subcat-count 3,30", + "key": "en_category-subcat-count", + "args": [ + 3, + 30 + ], + "result": "This category has the following 3 subcategories, out of 30 total.", + "lang": "en" + }, + { + "name": "fr undelete_short 0", + "key": "fr_undelete_short", + "args": [ + 0 + ], + "result": "Restaurer 0 modification", + "lang": "fr" + }, + { + "name": "fr undelete_short 1", + "key": "fr_undelete_short", + "args": [ + 1 + ], + "result": "Restaurer 1 modification", + "lang": "fr" + }, + { + "name": "fr undelete_short 2", + "key": "fr_undelete_short", + "args": [ + 2 + ], + "result": "Restaurer 2 modifications", + "lang": "fr" + }, + { + "name": "fr undelete_short 5", + "key": "fr_undelete_short", + "args": [ + 5 + ], + "result": "Restaurer 5 modifications", + "lang": "fr" + }, + { + "name": "fr undelete_short 21", + "key": "fr_undelete_short", + "args": [ + 21 + ], + "result": "Restaurer 21 modifications", + "lang": "fr" + }, + { + "name": "fr undelete_short 101", + "key": "fr_undelete_short", + "args": [ + 101 + ], + "result": "Restaurer 101 modifications", + "lang": "fr" + }, + { + "name": "fr category-subcat-count 0,10", + "key": "fr_category-subcat-count", + "args": [ + 0, + 10 + ], + "result": "Cette cat\u00e9gorie comprend 10 sous-cat\u00e9gories, dont celle ci-dessous.", + "lang": "fr" + }, + { + "name": "fr category-subcat-count 1,1", + "key": "fr_category-subcat-count", + "args": [ + 1, + 1 + ], + "result": "Cette cat\u00e9gorie comprend la sous-cat\u00e9gorie ci-dessous.", + "lang": "fr" + }, + { + "name": "fr category-subcat-count 1,2", + "key": "fr_category-subcat-count", + "args": [ + 1, + 2 + ], + "result": "Cette cat\u00e9gorie comprend 2 sous-cat\u00e9gories, dont celle ci-dessous.", + "lang": "fr" + }, + { + "name": "fr category-subcat-count 3,30", + "key": "fr_category-subcat-count", + "args": [ + 3, + 30 + ], + "result": "Cette cat\u00e9gorie comprend 30 sous-cat\u00e9gories, dont les 3 ci-dessous.", + "lang": "fr" + }, + { + "name": "ar undelete_short 0", + "key": "ar_undelete_short", + "args": [ + 0 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f", + "lang": "ar" + }, + { + "name": "ar undelete_short 1", + "key": "ar_undelete_short", + "args": [ + 1 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644\u064a\u0646", + "lang": "ar" + }, + { + "name": "ar undelete_short 2", + "key": "ar_undelete_short", + "args": [ + 2 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 2 \u062a\u0639\u062f\u064a\u0644\u0627\u062a", + "lang": "ar" + }, + { + "name": "ar undelete_short 5", + "key": "ar_undelete_short", + "args": [ + 5 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 5 \u062a\u0639\u062f\u064a\u0644", + "lang": "ar" + }, + { + "name": "ar undelete_short 21", + "key": "ar_undelete_short", + "args": [ + 21 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 21 \u062a\u0639\u062f\u064a\u0644\u0627", + "lang": "ar" + }, + { + "name": "ar undelete_short 101", + "key": "ar_undelete_short", + "args": [ + 101 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 101 \u062a\u0639\u062f\u064a\u0644\u0627", + "lang": "ar" + }, + { + "name": "ar category-subcat-count 0,10", + "key": "ar_category-subcat-count", + "args": [ + 0, + 10 + ], + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 10.", + "lang": "ar" + }, + { + "name": "ar category-subcat-count 1,1", + "key": "ar_category-subcat-count", + "args": [ + 1, + 1 + ], + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.", + "lang": "ar" + }, + { + "name": "ar category-subcat-count 1,2", + "key": "ar_category-subcat-count", + "args": [ + 1, + 2 + ], + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 2.", + "lang": "ar" + }, + { + "name": "ar category-subcat-count 3,30", + "key": "ar_category-subcat-count", + "args": [ + 3, + 30 + ], + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0647 \u0627\u06443 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 30.", + "lang": "ar" + }, + { + "name": "jp undelete_short 0", + "key": "jp_undelete_short", + "args": [ + 0 + ], + "result": "Undelete 0 edits", + "lang": "jp" + }, + { + "name": "jp undelete_short 1", + "key": "jp_undelete_short", + "args": [ + 1 + ], + "result": "Undelete one edit", + "lang": "jp" + }, + { + "name": "jp undelete_short 2", + "key": "jp_undelete_short", + "args": [ + 2 + ], + "result": "Undelete 2 edits", + "lang": "jp" + }, + { + "name": "jp undelete_short 5", + "key": "jp_undelete_short", + "args": [ + 5 + ], + "result": "Undelete 5 edits", + "lang": "jp" + }, + { + "name": "jp undelete_short 21", + "key": "jp_undelete_short", + "args": [ + 21 + ], + "result": "Undelete 21 edits", + "lang": "jp" + }, + { + "name": "jp undelete_short 101", + "key": "jp_undelete_short", + "args": [ + 101 + ], + "result": "Undelete 101 edits", + "lang": "jp" + }, + { + "name": "jp category-subcat-count 0,10", + "key": "jp_category-subcat-count", + "args": [ + 0, + 10 + ], + "result": "This category has the following 0 subcategories, out of 10 total.", + "lang": "jp" + }, + { + "name": "jp category-subcat-count 1,1", + "key": "jp_category-subcat-count", + "args": [ + 1, + 1 + ], + "result": "This category has only the following subcategory.", + "lang": "jp" + }, + { + "name": "jp category-subcat-count 1,2", + "key": "jp_category-subcat-count", + "args": [ + 1, + 2 + ], + "result": "This category has the following subcategory, out of 2 total.", + "lang": "jp" + }, + { + "name": "jp category-subcat-count 3,30", + "key": "jp_category-subcat-count", + "args": [ + 3, + 30 + ], + "result": "This category has the following 3 subcategories, out of 30 total.", + "lang": "jp" + }, + { + "name": "zh undelete_short 0", + "key": "zh_undelete_short", + "args": [ + 0 + ], + "result": "\u6062\u590d\u88ab\u5220\u9664\u76840\u9879\u4fee\u8ba2", + "lang": "zh" + }, + { + "name": "zh undelete_short 1", + "key": "zh_undelete_short", + "args": [ + 1 + ], + "result": "\u6062\u590d\u88ab\u5220\u9664\u76841\u9879\u4fee\u8ba2", + "lang": "zh" + }, + { + "name": "zh undelete_short 2", + "key": "zh_undelete_short", + "args": [ + 2 + ], + "result": "\u6062\u590d\u88ab\u5220\u9664\u76842\u9879\u4fee\u8ba2", + "lang": "zh" + }, + { + "name": "zh undelete_short 5", + "key": "zh_undelete_short", + "args": [ + 5 + ], + "result": "\u6062\u590d\u88ab\u5220\u9664\u76845\u9879\u4fee\u8ba2", + "lang": "zh" + }, + { + "name": "zh undelete_short 21", + "key": "zh_undelete_short", + "args": [ + 21 + ], + "result": "\u6062\u590d\u88ab\u5220\u9664\u768421\u9879\u4fee\u8ba2", + "lang": "zh" + }, + { + "name": "zh undelete_short 101", + "key": "zh_undelete_short", + "args": [ + 101 + ], + "result": "\u6062\u590d\u88ab\u5220\u9664\u7684101\u9879\u4fee\u8ba2", + "lang": "zh" + }, + { + "name": "zh category-subcat-count 0,10", + "key": "zh_category-subcat-count", + "args": [ + 0, + 10 + ], + "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52170\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670910\u4e2a\u5b50\u5206\u7c7b\u3002", + "lang": "zh" + }, + { + "name": "zh category-subcat-count 1,1", + "key": "zh_category-subcat-count", + "args": [ + 1, + 1 + ], + "result": "\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002", + "lang": "zh" + }, + { + "name": "zh category-subcat-count 1,2", + "key": "zh_category-subcat-count", + "args": [ + 1, + 2 + ], + "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52171\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u67092\u4e2a\u5b50\u5206\u7c7b\u3002", + "lang": "zh" + }, + { + "name": "zh category-subcat-count 3,30", + "key": "zh_category-subcat-count", + "args": [ + 3, + 30 + ], + "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52173\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670930\u4e2a\u5b50\u5206\u7c7b\u3002", + "lang": "zh" + } +]; diff --git a/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js new file mode 100644 index 00000000..46dcaa80 --- /dev/null +++ b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js @@ -0,0 +1,389 @@ +/* spec for language & message behaviour in MediaWiki */ + +mw.messages.set( { + "en_empty": "", + "en_simple": "Simple message", + "en_replace": "Simple $1 replacement", + "en_replace2": "Simple $1 $2 replacements", + "en_link": "Simple [http://example.com link to example].", + "en_link_replace": "Complex [$1 $2] behaviour.", + "en_simple_magic": "Simple {{ALOHOMORA}} message", + "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}", + "en_undelete_empty_param": "Undelete{{PLURAL:$1|| multiple edits}}", + "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}", + "en_escape0": "Escape \\to fantasy island", + "en_escape1": "I had \\$2.50 in my pocket", + "en_escape2": "I had {{PLURAL:$1|the absolute \\|$1\\| which came out to \\$3.00 in my C:\\\\drive| some stuff}}", + "en_fail": "This should fail to {{parse", + "en_fail_magic": "There is no such magic word as {{SIETNAME}}", + "en_evil": "This has <script type='text/javascript'>window.en_evil = true;</script> tags" +} ); + +/** + * Tests + */ +( function( mw, $, undefined ) { + + describe( "mediaWiki.jqueryMsg", function() { + + describe( "basic message functionality", function() { + + it( "should return identity for empty string", function() { + var parser = new mw.jqueryMsg.parser(); + expect( parser.parse( 'en_empty' ).html() ).toEqual( '' ); + } ); + + + it( "should return identity for simple string", function() { + var parser = new mw.jqueryMsg.parser(); + expect( parser.parse( 'en_simple' ).html() ).toEqual( 'Simple message' ); + } ); + + } ); + + describe( "escaping", function() { + + it ( "should handle simple escaping", function() { + var parser = new mw.jqueryMsg.parser(); + expect( parser.parse( 'en_escape0' ).html() ).toEqual( 'Escape to fantasy island' ); + } ); + + it ( "should escape dollar signs found in ordinary text when backslashed", function() { + var parser = new mw.jqueryMsg.parser(); + expect( parser.parse( 'en_escape1' ).html() ).toEqual( 'I had $2.50 in my pocket' ); + } ); + + it ( "should handle a complicated escaping case, including escaped pipe chars in template args", function() { + var parser = new mw.jqueryMsg.parser(); + expect( parser.parse( 'en_escape2', [ 1 ] ).html() ).toEqual( 'I had the absolute |1| which came out to $3.00 in my C:\\drive' ); + } ); + + } ); + + describe( "replacing", function() { + + it ( "should handle simple replacing", function() { + var parser = new mw.jqueryMsg.parser(); + expect( parser.parse( 'en_replace', [ 'foo' ] ).html() ).toEqual( 'Simple foo replacement' ); + } ); + + it ( "should return $n if replacement not there", function() { + var parser = new mw.jqueryMsg.parser(); + expect( parser.parse( 'en_replace', [] ).html() ).toEqual( 'Simple $1 replacement' ); + expect( parser.parse( 'en_replace2', [ 'bar' ] ).html() ).toEqual( 'Simple bar $2 replacements' ); + } ); + + } ); + + describe( "linking", function() { + + it ( "should handle a simple link", function() { + var parser = new mw.jqueryMsg.parser(); + var parsed = parser.parse( 'en_link' ); + var contents = parsed.contents(); + expect( contents.length ).toEqual( 3 ); + expect( contents[0].nodeName ).toEqual( '#text' ); + expect( contents[0].nodeValue ).toEqual( 'Simple ' ); + expect( contents[1].nodeName ).toEqual( 'A' ); + expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com' ); + expect( contents[1].childNodes[0].nodeValue ).toEqual( 'link to example' ); + expect( contents[2].nodeName ).toEqual( '#text' ); + expect( contents[2].nodeValue ).toEqual( '.' ); + } ); + + it ( "should replace a URL into a link", function() { + var parser = new mw.jqueryMsg.parser(); + var parsed = parser.parse( 'en_link_replace', [ 'http://example.com/foo', 'linking' ] ); + var contents = parsed.contents(); + expect( contents.length ).toEqual( 3 ); + expect( contents[0].nodeName ).toEqual( '#text' ); + expect( contents[0].nodeValue ).toEqual( 'Complex ' ); + expect( contents[1].nodeName ).toEqual( 'A' ); + expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com/foo' ); + expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' ); + expect( contents[2].nodeName ).toEqual( '#text' ); + expect( contents[2].nodeValue ).toEqual( ' behaviour.' ); + } ); + + it ( "should bind a click handler into a link", function() { + var parser = new mw.jqueryMsg.parser(); + var clicked = false; + var click = function() { clicked = true; }; + var parsed = parser.parse( 'en_link_replace', [ click, 'linking' ] ); + var contents = parsed.contents(); + expect( contents.length ).toEqual( 3 ); + expect( contents[0].nodeName ).toEqual( '#text' ); + expect( contents[0].nodeValue ).toEqual( 'Complex ' ); + expect( contents[1].nodeName ).toEqual( 'A' ); + expect( contents[1].getAttribute( 'href' ) ).toEqual( '#' ); + expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' ); + expect( contents[2].nodeName ).toEqual( '#text' ); + expect( contents[2].nodeValue ).toEqual( ' behaviour.' ); + // determining bindings is hard in IE + var anchor = parsed.find( 'a' ); + if ( ( $.browser.mozilla || $.browser.webkit ) && anchor.click ) { + expect( clicked ).toEqual( false ); + anchor.click(); + expect( clicked ).toEqual( true ); + } + } ); + + it ( "should wrap a jquery arg around link contents -- even another element", function() { + var parser = new mw.jqueryMsg.parser(); + var clicked = false; + var click = function() { clicked = true; }; + var button = $( '<button>' ).click( click ); + var parsed = parser.parse( 'en_link_replace', [ button, 'buttoning' ] ); + var contents = parsed.contents(); + expect( contents.length ).toEqual( 3 ); + expect( contents[0].nodeName ).toEqual( '#text' ); + expect( contents[0].nodeValue ).toEqual( 'Complex ' ); + expect( contents[1].nodeName ).toEqual( 'BUTTON' ); + expect( contents[1].childNodes[0].nodeValue ).toEqual( 'buttoning' ); + expect( contents[2].nodeName ).toEqual( '#text' ); + expect( contents[2].nodeValue ).toEqual( ' behaviour.' ); + // determining bindings is hard in IE + if ( ( $.browser.mozilla || $.browser.webkit ) && button.click ) { + expect( clicked ).toEqual( false ); + parsed.find( 'button' ).click(); + expect( clicked ).toEqual( true ); + } + } ); + + + } ); + + + describe( "magic keywords", function() { + it( "should substitute magic keywords", function() { + var options = { + magic: { + 'alohomora' : 'open' + } + }; + var parser = new mw.jqueryMsg.parser( options ); + expect( parser.parse( 'en_simple_magic' ).html() ).toEqual( 'Simple open message' ); + } ); + } ); + + describe( "error conditions", function() { + it( "should return non-existent key in square brackets", function() { + var parser = new mw.jqueryMsg.parser(); + expect( parser.parse( 'en_does_not_exist' ).html() ).toEqual( '[en_does_not_exist]' ); + } ); + + + it( "should fail to parse", function() { + var parser = new mw.jqueryMsg.parser(); + expect( function() { parser.parse( 'en_fail' ); } ).toThrow( + 'Parse error at position 20 in input: This should fail to {{parse' + ); + } ); + } ); + + describe( "empty parameters", function() { + it( "should deal with empty parameters", function() { + var parser = new mw.jqueryMsg.parser(); + var ast = parser.getAst( 'en_undelete_empty_param' ); + expect( parser.parse( 'en_undelete_empty_param', [ 1 ] ).html() ).toEqual( 'Undelete' ); + expect( parser.parse( 'en_undelete_empty_param', [ 3 ] ).html() ).toEqual( 'Undelete multiple edits' ); + + } ); + } ); + + describe( "easy message interface functions", function() { + it( "should allow a global that returns strings", function() { + var gM = mw.jqueryMsg.getMessageFunction(); + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. + // a surrounding <SPAN> is needed for html() to work right + var expectedHtml = $( '<span>Complex <a href="http://example.com/foo">linking</a> behaviour.</span>' ).html(); + var result = gM( 'en_link_replace', 'http://example.com/foo', 'linking' ); + expect( typeof result ).toEqual( 'string' ); + expect( result ).toEqual( expectedHtml ); + } ); + + it( "should allow a jQuery plugin that appends to nodes", function() { + $.fn.msg = mw.jqueryMsg.getPlugin(); + var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) ); + var clicked = false; + var $button = $( '<button>' ).click( function() { clicked = true; } ); + $div.find( '.foo' ).msg( 'en_link_replace', $button, 'buttoning' ); + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. + // a surrounding <SPAN> is needed for html() to work right + var expectedHtml = $( '<span>Complex <button>buttoning</button> behaviour.</span>' ).html(); + var createdHtml = $div.find( '.foo' ).html(); + // it is hard to test for clicks with IE; also it inserts or removes spaces around nodes when creating HTML tags, depending on their type. + // so need to check the strings stripped of spaces. + if ( ( $.browser.mozilla || $.browser.webkit ) && $button.click ) { + expect( createdHtml ).toEqual( expectedHtml ); + $div.find( 'button ').click(); + expect( clicked ).toEqual( true ); + } else if ( $.browser.ie ) { + expect( createdHtml.replace( /\s/, '' ) ).toEqual( expectedHtml.replace( /\s/, '' ) ); + } + delete $.fn.msg; + } ); + + it( "jQuery plugin should escape incoming string arguments", function() { + $.fn.msg = mw.jqueryMsg.getPlugin(); + var $div = $( '<div>' ).addClass( 'foo' ); + $div.msg( 'en_replace', '<p>x</p>' ); // looks like HTML, but as a string, should be escaped. + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. + var expectedHtml = $( '<div class="foo">Simple <p>x</p> replacement</div>' ).html(); + var createdHtml = $div.html(); + expect( expectedHtml ).toEqual( createdHtml ); + delete $.fn.msg; + } ); + + + it( "jQuery plugin should never execute scripts", function() { + window.en_evil = false; + $.fn.msg = mw.jqueryMsg.getPlugin(); + var $div = $( '<div>' ); + $div.msg( 'en_evil' ); + expect( window.en_evil ).toEqual( false ); + delete $.fn.msg; + } ); + + + // n.b. this passes because jQuery already seems to strip scripts away; however, it still executes them if they are appended to any element. + it( "jQuery plugin should never emit scripts", function() { + $.fn.msg = mw.jqueryMsg.getPlugin(); + var $div = $( '<div>' ); + $div.msg( 'en_evil' ); + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. + var expectedHtml = $( '<div>This has tags</div>' ).html(); + var createdHtml = $div.html(); + expect( expectedHtml ).toEqual( createdHtml ); + console.log( 'expected: ' + expectedHtml ); + console.log( 'created: ' + createdHtml ); + delete $.fn.msg; + } ); + + + + } ); + + // The parser functions can throw errors, but let's not actually blow up for the user -- instead dump the error into the interface so we have + // a chance at fixing this + describe( "easy message interface functions with graceful failures", function() { + it( "should allow a global that returns strings, with graceful failure", function() { + var gM = mw.jqueryMsg.getMessageFunction(); + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. + // a surrounding <SPAN> is needed for html() to work right + var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html(); + var result = gM( 'en_fail' ); + expect( typeof result ).toEqual( 'string' ); + expect( result ).toEqual( expectedHtml ); + } ); + + it( "should allow a global that returns strings, with graceful failure on missing magic words", function() { + var gM = mw.jqueryMsg.getMessageFunction(); + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. + // a surrounding <SPAN> is needed for html() to work right + var expectedHtml = $( '<span>en_fail_magic: unknown operation "sietname"</span>' ).html(); + var result = gM( 'en_fail_magic' ); + expect( typeof result ).toEqual( 'string' ); + expect( result ).toEqual( expectedHtml ); + } ); + + + it( "should allow a jQuery plugin, with graceful failure", function() { + $.fn.msg = mw.jqueryMsg.getPlugin(); + var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) ); + $div.find( '.foo' ).msg( 'en_fail' ); + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. + // a surrounding <SPAN> is needed for html() to work right + var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html(); + var createdHtml = $div.find( '.foo' ).html(); + expect( createdHtml ).toEqual( expectedHtml ); + delete $.fn.msg; + } ); + + } ); + + + + + describe( "test plurals and other language-specific functions", function() { + /* copying some language definitions in here -- it's hard to make this test fast and reliable + otherwise, and we don't want to have to know the mediawiki URL from this kind of test either. + We also can't preload the langs for the test since they clobber the same namespace. + In principle Roan said it was okay to change how languages worked so that didn't happen... maybe + someday. We'd have to the same kind of importing of the default rules for most rules, or maybe + come up with some kind of subclassing scheme for languages */ + var languageClasses = { + ar: { + /** + * Arabic (العربية) language functions + */ + + convertPlural: function( count, forms ) { + forms = mw.language.preConvertPlural( forms, 6 ); + if ( count === 0 ) { + return forms[0]; + } + if ( count == 1 ) { + return forms[1]; + } + if ( count == 2 ) { + return forms[2]; + } + if ( count % 100 >= 3 && count % 100 <= 10 ) { + return forms[3]; + } + if ( count % 100 >= 11 && count % 100 <= 99 ) { + return forms[4]; + } + return forms[5]; + }, + + digitTransformTable: { + '0': '٠', // ٠ + '1': '١', // ١ + '2': '٢', // ٢ + '3': '٣', // ٣ + '4': '٤', // ٤ + '5': '٥', // ٥ + '6': '٦', // ٦ + '7': '٧', // ٧ + '8': '٨', // ٨ + '9': '٩', // ٩ + '.': '٫', // ٫ wrong table ? + ',': '٬' // ٬ + } + + }, + en: { }, + fr: { + convertPlural: function( count, forms ) { + forms = mw.language.preConvertPlural( forms, 2 ); + return ( count <= 1 ) ? forms[0] : forms[1]; + } + }, + jp: { }, + zh: { } + }; + + /* simulate how the language classes override, or don't, the standard functions in mw.language */ + $.each( languageClasses, function( langCode, rules ) { + $.each( [ 'convertPlural', 'convertNumber' ], function( i, propertyName ) { + if ( typeof rules[ propertyName ] === 'undefined' ) { + rules[ propertyName ] = mw.language[ propertyName ]; + } + } ); + } ); + + $.each( jasmineMsgSpec, function( i, test ) { + it( "should parse " + test.name, function() { + // using language override so we don't have to muck with global namespace + var parser = new mw.jqueryMsg.parser( { language: languageClasses[ test.lang ] } ); + var parsedHtml = parser.parse( test.key, test.args ).html(); + expect( parsedHtml ).toEqual( test.result ); + } ); + } ); + + } ); + + } ); +} )( window.mediaWiki, jQuery ); diff --git a/tests/jasmine/spec_makers/makeJqueryMsgSpec.php b/tests/jasmine/spec_makers/makeJqueryMsgSpec.php new file mode 100644 index 00000000..1ac8dcba --- /dev/null +++ b/tests/jasmine/spec_makers/makeJqueryMsgSpec.php @@ -0,0 +1,114 @@ +<?php + +/** + * This PHP script defines the spec that the Javascript message parser should conform to. + * + * It does this by looking up the results of various string kinds of string parsing, with various languages, + * in the current installation of MediaWiki. It then outputs a static specification, mapping expected inputs to outputs, + * which can be used with the JasmineBDD framework. This specification can then be used by simply including it into + * the SpecRunner.html file. + * + * This is similar to Michael Dale (mdale@mediawiki.org)'s parser tests, except that it doesn't look up the + * API results while doing the test, so the Jasmine run is much faster(at the cost of being out of date in rare + * circumstances. But mostly the parsing that we are doing in Javascript doesn't change much.) + * + */ + +$maintenanceDir = dirname( dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) ) . '/maintenance'; + +require( "$maintenanceDir/Maintenance.php" ); + +class MakeLanguageSpec extends Maintenance { + + static $keyToTestArgs = array( + 'undelete_short' => array( + array( 0 ), + array( 1 ), + array( 2 ), + array( 5 ), + array( 21 ), + array( 101 ) + ), + 'category-subcat-count' => array( + array( 0, 10 ), + array( 1, 1 ), + array( 1, 2 ), + array( 3, 30 ) + ) + ); + + public function __construct() { + parent::__construct(); + $this->mDescription = "Create a JasmineBDD-compatible specification for message parsing"; + // add any other options here + } + + public function execute() { + list( $messages, $tests ) = $this->getMessagesAndTests(); + $this->writeJavascriptFile( $messages, $tests, "spec/mediawiki.language.parser.spec.data.js" ); + } + + private function getMessagesAndTests() { + $messages = array(); + $tests = array(); + $wfMsgExtOptions = array( 'parsemag' ); + foreach ( array( 'en', 'fr', 'ar', 'jp', 'zh' ) as $languageCode ) { + $wfMsgExtOptions['language'] = $languageCode; + foreach ( self::$keyToTestArgs as $key => $testArgs ) { + foreach ($testArgs as $args) { + // get the raw template, without any transformations + $template = wfMsgGetKey( $key, /* useDb */ true, $languageCode, /* transform */ false ); + + // get the magic-parsed version with args + $wfMsgExtArgs = array_merge( array( $key, $wfMsgExtOptions ), $args ); + $result = call_user_func_array( 'wfMsgExt', $wfMsgExtArgs ); + + // record the template, args, language, and expected result + // fake multiple languages by flattening them together + $langKey = $languageCode . '_' . $key; + $messages[ $langKey ] = $template; + $tests[] = array( + 'name' => $languageCode . " " . $key . " " . join( ",", $args ), + 'key' => $langKey, + 'args' => $args, + 'result' => $result, + 'lang' => $languageCode + ); + } + } + } + return array( $messages, $tests ); + } + + private function writeJavascriptFile( $messages, $tests, $dataSpecFile ) { + global $argv; + $arguments = count($argv) ? $argv : $_SERVER[ 'argv' ]; + + $json = new Services_JSON; + $json->pretty = true; + $javascriptPrologue = "// This file stores the results from the PHP parser for certain messages and arguments,\n" + . "// so we can test the equivalent Javascript libraries.\n" + . '// Last generated with ' . join(' ', $arguments) . ' at ' . gmdate('c') . "\n\n"; + $javascriptMessages = "mediaWiki.messages.set( " . $json->encode( $messages, true ) . " );\n"; + $javascriptTests = 'var jasmineMsgSpec = ' . $json->encode( $tests, true ) . ";\n"; + + $fp = fopen( $dataSpecFile, 'w' ); + if ( !$fp ) { + die( "couldn't open $dataSpecFile for writing" ); + } + $success = fwrite( $fp, $javascriptPrologue . $javascriptMessages . $javascriptTests ); + if ( !$success ) { + die( "couldn't write to $dataSpecFile" ); + } + $success = fclose( $fp ); + if ( !$success ) { + die( "couldn't close $dataSpecFile" ); + } + } +} + +$maintClass = "MakeLanguageSpec"; +require_once( "$maintenanceDir/doMaintenance.php" ); + + + diff --git a/tests/parser/README b/tests/parser/README new file mode 100644 index 00000000..8b413376 --- /dev/null +++ b/tests/parser/README @@ -0,0 +1,8 @@ +Parser tests are run using our PHPUnit test suite in tests/phpunit: + + $ cd tests/phpunit + ./phpunit.php --group Parser + +You can optionally filter by title using --regex. I.e. : + + ./phpunit.php --group Parser --regex="Bug 6200" diff --git a/tests/parser/parserTest.inc b/tests/parser/parserTest.inc index 0ce7c997..30e451b3 100644 --- a/tests/parser/parserTest.inc +++ b/tests/parser/parserTest.inc @@ -105,6 +105,9 @@ class ParserTest { $this->showOutput = isset( $options['show-output'] ); + if ( isset( $options['filter'] ) ) { + $options['regex'] = $options['filter']; + } if ( isset( $options['regex'] ) ) { if ( isset( $options['record'] ) ) { @@ -132,45 +135,65 @@ class ParserTest { } static function setUp() { - global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgDeferredUpdateList, + global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache, $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo, $parserMemc, $wgThumbnailScriptPath, $wgScriptPath, - $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath; + $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath, $wgExtensionAssetsPath, + $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType; $wgScript = '/index.php'; $wgScriptPath = '/'; $wgArticlePath = '/wiki/$1'; $wgStyleSheetPath = '/skins'; $wgStylePath = '/skins'; + $wgExtensionAssetsPath = '/extensions'; $wgThumbnailScriptPath = false; $wgLocalFileRepo = array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'directory' => wfTempDir() . '/test-repo', - 'url' => 'http://example.com/images', - 'deletedDir' => wfTempDir() . '/test-repo/delete', - 'hashLevels' => 2, + 'class' => 'LocalRepo', + 'name' => 'local', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, 'transformVia404' => false, + 'backend' => new FSFileBackend( array( + 'name' => 'local-backend', + 'lockManager' => 'fsLockManager', + 'containerPaths' => array( + 'local-public' => wfTempDir() . '/test-repo/public', + 'local-thumb' => wfTempDir() . '/test-repo/thumb', + 'local-temp' => wfTempDir() . '/test-repo/temp', + 'local-deleted' => wfTempDir() . '/test-repo/deleted', + ) + ) ) ); $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface'; $wgNamespaceAliases['Image'] = NS_FILE; $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; + // XXX: tests won't run without this (for CACHE_DB) + if ( $wgMainCacheType === CACHE_DB ) { + $wgMainCacheType = CACHE_NONE; + } + if ( $wgMessageCacheType === CACHE_DB ) { + $wgMessageCacheType = CACHE_NONE; + } + if ( $wgParserCacheType === CACHE_DB ) { + $wgParserCacheType = CACHE_NONE; + } $wgEnableParserCache = false; - $wgDeferredUpdateList = array(); - $wgMemc = wfGetMainCache(); + DeferredUpdates::clearPendingUpdates(); + $wgMemc = wfGetMainCache(); // checks $wgMainCacheType $messageMemc = wfGetMessageCacheStorage(); $parserMemc = wfGetParserCacheStorage(); // $wgContLang = new StubContLang; $wgUser = new User; $context = new RequestContext; - $wgLang = $context->getLang(); + $wgLang = $context->getLanguage(); $wgOut = $context->getOutput(); $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) ); - $wgRequest = new WebRequest; + $wgRequest = $context->getRequest(); if ( $wgStyleDirectory === false ) { $wgStyleDirectory = "$IP/skins"; @@ -423,10 +446,10 @@ class ParserTest { } $opts = $this->parseOptions( $opts ); - $this->setupGlobals( $opts, $config ); + $context = $this->setupGlobals( $opts, $config ); - $user = new User(); - $options = ParserOptions::newFromUser( $user ); + $user = $context->getUser(); + $options = ParserOptions::newFromContext( $context ); if ( isset( $opts['title'] ) ) { $titleText = $opts['title']; @@ -452,8 +475,7 @@ class ParserTest { $replace = $opts['replace'][1]; $out = $parser->replaceSection( $input, $section, $replace ); } elseif ( isset( $opts['comment'] ) ) { - $linker = $user->getSkin(); - $out = $linker->formatComment( $input, $title, $local ); + $out = Linker::formatComment( $input, $title, $local ); } elseif ( isset( $opts['preload'] ) ) { $out = $parser->getpreloadText( $input, $title, $options ); } else { @@ -471,10 +493,9 @@ class ParserTest { if ( isset( $opts['ill'] ) ) { $out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) ); } elseif ( isset( $opts['cat'] ) ) { - global $wgOut; - - $wgOut->addCategoryLinks( $output->getCategories() ); - $cats = $wgOut->getCategoryLinks(); + $outputPage = $context->getOutput(); + $outputPage->addCategoryLinks( $output->getCategories() ); + $cats = $outputPage->getCategoryLinks(); if ( isset( $cats['normal'] ) ) { $out = $this->tidy( implode( ' ', $cats['normal'] ) ); @@ -609,10 +630,19 @@ class ParserTest { 'wgLocalFileRepo' => array( 'class' => 'LocalRepo', 'name' => 'local', - 'directory' => $this->uploadDir, 'url' => 'http://example.com/images', 'hashLevels' => 2, 'transformVia404' => false, + 'backend' => new FSFileBackend( array( + 'name' => 'local-backend', + 'lockManager' => 'fsLockManager', + 'containerPaths' => array( + 'local-public' => $this->uploadDir, + 'local-thumb' => $this->uploadDir . '/thumb', + 'local-temp' => $this->uploadDir . '/temp', + 'local-deleted' => $this->uploadDir . '/delete', + ) + ) ) ), 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ), 'wgStylePath' => '/skins', @@ -650,6 +680,7 @@ class ParserTest { 'wgExternalLinkTarget' => false, 'wgAlwaysUseTidy' => false, 'wgHtml5' => true, + 'wgCleanupPresentationalAttributes' => true, 'wgWellFormedXml' => true, 'wgAllowMicrodataAttributes' => true, 'wgAdaptiveMessageCache' => true, @@ -681,7 +712,7 @@ class ParserTest { $GLOBALS['wgMemc'] = new EmptyBagOStuff; $context = new RequestContext(); - $GLOBALS['wgLang'] = $context->getLang(); + $GLOBALS['wgLang'] = $context->getLanguage(); $GLOBALS['wgOut'] = $context->getOutput(); $GLOBALS['wgUser'] = new User(); @@ -689,10 +720,11 @@ class ParserTest { global $wgHooks; $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup'; - $wgHooks['ParserTestParser'][] = 'ParserTestStaticParserHook::setup'; $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; MagicWord::clearCache(); + + return $context; } /** @@ -700,7 +732,7 @@ class ParserTest { * Some of these probably aren't necessary. */ private function listTables() { - $tables = array( 'user', 'user_properties', 'page', 'page_restrictions', + $tables = array( 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions', 'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks', 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks', 'site_stats', 'hitcounter', 'ipblocks', 'image', 'oldimage', @@ -709,8 +741,9 @@ class ParserTest { 'archive', 'user_groups', 'page_props', 'category', 'msg_resource', 'msg_resource_links' ); - if ( in_array( $this->db->getType(), array( 'mysql', 'sqlite', 'oracle' ) ) ) + if ( in_array( $this->db->getType(), array( 'mysql', 'sqlite', 'oracle' ) ) ) { array_push( $tables, 'searchindex' ); + } // Allow extensions to add to the list of tables to duplicate; // may be necessary if they hook into page save or other code @@ -753,17 +786,14 @@ class ParserTest { } $temporary = $this->useTemporaryTables || $dbType == 'postgres'; - $tables = $this->listTables(); $prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_'; $this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix ); $this->dbClone->useTemporaryTables( $temporary ); $this->dbClone->cloneTableStructure(); - if ( $dbType == 'oracle' ) - $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' ); - if ( $dbType == 'oracle' ) { + $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' ); # Insert 0 user to prevent FK violations # Anonymous user @@ -807,7 +837,6 @@ class ParserTest { 'iw_local' => 1 ), ) ); - # Update certain things in site_stats $this->db->insert( 'site_stats', array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ) ); @@ -908,9 +937,9 @@ class ParserTest { return $dir; } - wfMkdirParents( $dir . '/3/3a' ); + wfMkdirParents( $dir . '/3/3a', null, __METHOD__ ); copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" ); - wfMkdirParents( $dir . '/0/09' ); + wfMkdirParents( $dir . '/0/09', null, __METHOD__ ); copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" ); return $dir; @@ -1077,7 +1106,9 @@ class ParserTest { $shellInfile = wfEscapeShellArg($infile); $shellOutfile = wfEscapeShellArg($outfile); - $diff = wfIsWindows() + global $wgDiff3; + // we assume that people with diff3 also have usual diff + $diff = ( wfIsWindows() && !$wgDiff3 ) ? `fc $shellInfile $shellOutfile` : `diff -au $shellInfile $shellOutfile`; unlink( $infile ); @@ -1130,30 +1161,35 @@ class ParserTest { * @param $name String: the title, including any prefix * @param $text String: the article text * @param $line Integer: the input line number, for reporting errors + * @param $ignoreDuplicate Boolean: whether to silently ignore duplicate pages */ - static public function addArticle( $name, $text, $line = 'unknown' ) { + static public function addArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) { global $wgCapitalLinks; - $text = self::chomp($text); - $oldCapitalLinks = $wgCapitalLinks; $wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637 + $text = self::chomp( $text ); $name = self::chomp( $name ); + $title = Title::newFromText( $name ); if ( is_null( $title ) ) { - throw new MWException( "invalid title ('$name' => '$title') at line $line\n" ); + throw new MWException( "invalid title '$name' at line $line\n" ); } - $aid = $title->getArticleID( Title::GAID_FOR_UPDATE ); + $page = WikiPage::factory( $title ); + $page->loadPageData( 'fromdbmaster' ); - if ( $aid != 0 ) { - throw new MWException( "duplicate article '$name' at line $line\n" ); + if ( $page->exists() ) { + if ( $ignoreDuplicate == 'ignoreduplicate' ) { + return; + } else { + throw new MWException( "duplicate article '$name' at line $line\n" ); + } } - $art = new Article( $title ); - $art->doEdit( $text, '', EDIT_NEW ); + $page->doEdit( $text, '', EDIT_NEW ); $wgCapitalLinks = $oldCapitalLinks; } @@ -1204,7 +1240,7 @@ class ParserTest { return true; } - /* + /** * Run the "tidy" command on text if the $wgUseTidy * global is true * diff --git a/tests/parser/parserTests.txt b/tests/parser/parserTests.txt index 999cd717..d304b19c 100644 --- a/tests/parser/parserTests.txt +++ b/tests/parser/parserTests.txt @@ -615,6 +615,26 @@ disabled </dl> !! end +!! test +Definition and unordered list using wiki syntax nested in unordered list using html tags. +!! input +<ul><li> +; term : description +* unordered +</li> +</ul> +!! result +<ul><li> +<dl><dt> term </dt><dd> description +</dd></dl> +<ul><li> unordered +</li></ul> +</li> +</ul> + +!! end + + ### ### External links ### @@ -1050,8 +1070,10 @@ http://www.example.com/?title=AT%26T </p> !! end +# According to http://dev.w3.org/html5/spec/Overview.html#parsing-urls a plain +# % is actually legal in HTML5. Any change in output would need testing though. !! test -Bug 4781, 5267: %26 in URL +Bug 4781, 5267: %25 in URL !! input http://www.example.com/?title=100%25_Bran !! result @@ -1169,6 +1191,29 @@ URL-encoding in URL functions (multiple parameters) </p> !! end +!! test +Brackets in urls +!! input +http://example.com/index.php?foozoid%5B%5D=bar + +http://example.com/index.php?foozoid[]=bar +!! result +<p><a rel="nofollow" class="external free" href="http://example.com/index.php?foozoid%5B%5D=bar">http://example.com/index.php?foozoid%5B%5D=bar</a> +</p><p><a rel="nofollow" class="external free" href="http://example.com/index.php?foozoid%5B%5D=bar">http://example.com/index.php?foozoid%5B%5D=bar</a> +</p> +!! end + +!! test +IPv6 urls (bug 21261) +!! options +disabled +!! input +http://[2404:130:0:1000::187:2]/index.php +!! result +<p><a rel="nofollow" class="external free" href="http://[2404:130:0:1000::187:2]/index.php">http://[2404:130:0:1000::187:2]/index.php</a> +</p> +!! end + ### ### Quotes ### @@ -1346,7 +1391,7 @@ Multiplication table !! test Table rowspan !! input -{| align=right border=1 +{| border=1 | Cell 1, row 1 |rowspan=2| Cell 2, row 1 (and 2) | Cell 3, row 1 @@ -1355,7 +1400,7 @@ Table rowspan | Cell 3, row 2 |} !! result -<table align="right" border="1"> +<table border="1"> <tr> <td> Cell 1, row 1 </td> @@ -1435,6 +1480,28 @@ Table security: embedded pipes (http://lists.wikimedia.org/mailman/htdig/wikitec !! end +!! test +Indented table markup mixed with indented pre content (proposed in bug 6200) +!! input + <table> + <tr> + <td> + Text that should be rendered preformatted + </td> + </tr> + </table> +!! result + <table> + <tr> + <td> +<pre>Text that should be rendered preformatted +</pre> + </td> + </tr> + </table> + +!! end + ### ### Internal links @@ -1872,7 +1939,7 @@ Inline interwiki link with empty title (bug 2372) !! input [[MeatBall:]] !! result -<p><a href="http://www.usemod.com/cgi-bin/mb.pl?" class="extiw" title="meatball:">MeatBall:</a> +<p><a href="http://www.usemod.com/cgi-bin/mb.pl" class="extiw" title="meatball:">MeatBall:</a> </p> !! end @@ -1969,13 +2036,13 @@ Incorrecly removing closing slashes from correctly formed XHTML !! test Failing to transform badly formed HTML into correct XHTML !! input -<br clear=left> -<br clear=right> -<br clear=all> +<br style="clear: left;"> +<br style="clear: right;"> +<br style="clear: both;"> !! result -<p><br clear="left" /> -<br clear="right" /> -<br clear="all" /> +<p><br style="clear: left;" /> +<br style="clear: right;" /> +<br style="clear: both;" /> </p> !!end @@ -3027,35 +3094,6 @@ section=1 !! result ==Section 1== !! end -!! article -Template:Top-level template -!! text -{{Nested template}} -!! endarticle - -!! article -Template:Nested template -!! text -*Item 1 -*Item 2 -!! endarticle - -!! test -Line-start flag in a nested template call -!! input -*Item A -*Item B - -{{Top-level template}} -!! result -<ul><li>Item A -</li><li>Item B -</li></ul> -<ul><li>Item 1 -</li><li>Item 2 -</li></ul> - -!! end ### ### Pre-save transform tests @@ -3421,6 +3459,66 @@ pst title=[[Ns:Somearticle (IGNORED), Context]] !! end !! test +pre-save transform: context links ("pipe trick") with full-width parens and no space (Japanese and Chinese style, bug 30149) +!! options +pst +!! input +[[Article(context)|]] +[[Bar:Article(context)|]] +[[:Bar:Article(context)|]] +[[|Article(context)]] +[[Bar:X (Y) Z|]] +[[:Bar:X (Y) Z|]] +!! result +[[Article(context)|Article]] +[[Bar:Article(context)|Article]] +[[:Bar:Article(context)|Article]] +[[Article(context)]] +[[Bar:X (Y) Z|X (Y) Z]] +[[:Bar:X (Y) Z|X (Y) Z]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with full-width parens and space (Japanese and Chinese style, bug 30149) +!! options +pst +!! input +[[Article (context)|]] +[[Bar:Article (context)|]] +[[:Bar:Article (context)|]] +[[|Article (context)]] +[[Bar:X (Y) Z|]] +[[:Bar:X (Y) Z|]] +!! result +[[Article (context)|Article]] +[[Bar:Article (context)|Article]] +[[:Bar:Article (context)|Article]] +[[Article (context)]] +[[Bar:X (Y) Z|X (Y) Z]] +[[:Bar:X (Y) Z|X (Y) Z]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with parens and no space (Korean style, bug 30149) +!! options +pst +!! input +[[Article(context)|]] +[[Bar:Article(context)|]] +[[:Bar:Article(context)|]] +[[|Article(context)]] +[[Bar:X(Y)Z|]] +[[:Bar:X(Y)Z|]] +!! result +[[Article(context)|Article]] +[[Bar:Article(context)|Article]] +[[:Bar:Article(context)|Article]] +[[Article(context)]] +[[Bar:X(Y)Z|X(Y)Z]] +[[:Bar:X(Y)Z|X(Y)Z]] +!! end + +!! test pre-save transform: trim trailing empty lines !! options pst @@ -4487,9 +4585,9 @@ div with unquoted attribute !! test div with illegal double attributes !! input -<div align="center" align="right">HTML rocks</div> +<div id="a" id="b">HTML rocks</div> !! result -<div align="right">HTML rocks</div> +<div id="b">HTML rocks</div> !!end @@ -4519,9 +4617,9 @@ Table multiple attributes correction !! test DIV IN UPPERCASE !! input -<DIV ALIGN="center">HTML ROCKS</DIV> +<DIV ID="x">HTML ROCKS</DIV> !! result -<div align="center">HTML ROCKS</div> +<div id="x">HTML ROCKS</div> !!end @@ -8381,7 +8479,17 @@ comment title=[[Main Page]] !! input /* External links */ removed bogus entries !! result -<span class="autocomment"><a href="/wiki/Main_Page#External_links" title="Main Page">→</a>External links: </span> removed bogus entries +<a href="/wiki/Main_Page#External_links" title="Main Page">→</a><span dir="auto"><span class="autocomment">External links: </span> removed bogus entries</span> +!!end + +!! test +Edit comment with section link and text before it (non-local, eg in history list) +!! options +comment title=[[Main Page]] +!! input +pre-comment text /* External links */ removed bogus entries +!! result +pre-comment text - <a href="/wiki/Main_Page#External_links" title="Main Page">→</a><span dir="auto"><span class="autocomment">External links: </span> removed bogus entries</span> !!end !! test @@ -8391,7 +8499,7 @@ comment local title=[[Main Page]] !! input /* External links */ removed bogus entries !! result -<span class="autocomment"><a href="#External_links">→</a>External links: </span> removed bogus entries +<a href="#External_links">→</a><span dir="auto"><span class="autocomment">External links: </span> removed bogus entries</span> !!end !! test @@ -8478,7 +8586,7 @@ title=[[Main Page]] !!input /* __hello__world__ */ !! result -<span class="autocomment"><a href="/wiki/Main_Page#hello_world" title="Main Page">→</a>__hello__world__</span> +<a href="/wiki/Main_Page#hello_world" title="Main Page">→</a><span dir="auto"><span class="autocomment">__hello__world__</span></span> !! end !! test @@ -8493,6 +8601,8 @@ comment !! test Bad images - basic functionality +!! options +disabled !! input [[File:Bad.jpg]] !! result @@ -8500,6 +8610,8 @@ Bad images - basic functionality !! test Bad images - bug 16039: text after bad image disappears +!! options +disabled !! input Foo bar [[File:Bad.jpg]] @@ -8713,6 +8825,33 @@ Text's been normalized? </p> !! end +!! test +Bug 19052 U+3000 IDEOGRAPHIC SPACE should terminate free external links +!! input +http://www.example.org/ <-- U+3000 (vim: ^Vu3000) +!! result +<p><a rel="nofollow" class="external free" href="http://www.example.org/">http://www.example.org/</a> <-- U+3000 (vim: ^Vu3000) +</p> +!! end + +!! test +Bug 19052 U+3000 IDEOGRAPHIC SPACE should terminate bracketed external links +!! input +[http://www.example.org/ ideograms] +!! result +<p><a rel="nofollow" class="external text" href="http://www.example.org/">ideograms</a> +</p> +!! end + +!! test +Bug 19052 U+3000 IDEOGRAPHIC SPACE should terminate external images links +!! input +http://www.example.org/pic.png <-- U+3000 (vim: ^Vu3000) +!! result +<p><img src="http://www.example.org/pic.png" alt="pic.png" /> <-- U+3000 (vim: ^Vu3000) +</p> +!! end + !! article Mediawiki:loop1 !! text @@ -8743,6 +8882,22 @@ Bug 31098 Template which includes system messages which includes the template !! end !! test +Deprecated presentational attributes are converted to css +!! input +{| +| valign=top align=left width=100 height=25% | Asdf +|} +<ul type="disc"></ul> +!! result +<table> +<tr> +<td style="text-align: left; height: 25%; vertical-align: top; width: 100px;"> Asdf +</td></tr></table> +<ul style="list-style-type: disc;"></ul> + +!! end + +!! test Bug31490 Turkish: ucfirst 'blah' !! options language=tr @@ -8797,6 +8952,132 @@ language=en </p> !! end +!! test +Bug 26375: TOC with italics +!! options +title=[[Main Page]] +!! input +__TOC__ +== ''Lost'' episodes == +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#Lost_episodes"><span class="tocnumber">1</span> <span class="toctext"><i>Lost</i> episodes</span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Lost episodes">edit</a>]</span> <span class="mw-headline" id="Lost_episodes"> <i>Lost</i> episodes </span></h2> + +!! end + +!! test +Bug 26375: TOC with bold +!! options +title=[[Main Page]] +!! input +__TOC__ +== '''should be bold''' then normal text == +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#should_be_bold_then_normal_text"><span class="tocnumber">1</span> <span class="toctext"><b>should be bold</b> then normal text</span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: should be bold then normal text">edit</a>]</span> <span class="mw-headline" id="should_be_bold_then_normal_text"> <b>should be bold</b> then normal text </span></h2> + +!! end + +!! test +Bug 33845: Headings become cursive in TOC when they contain an image +!! options +title=[[Main Page]] +!! input +__TOC__ +== Image [[Image:foobar.jpg]] == +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#Image"><span class="tocnumber">1</span> <span class="toctext">Image</span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Image">edit</a>]</span> <span class="mw-headline" id="Image"> Image <a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </span></h2> + +!! end + +!! test +Bug 33845 (2): Headings become bold in TOC when they contain a blockquote +!! options +title=[[Main Page]] +!! input +__TOC__ +== <blockquote>Quote</blockquote> == +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#Quote"><span class="tocnumber">1</span> <span class="toctext">Quote</span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Quote">edit</a>]</span> <span class="mw-headline" id="Quote"> <blockquote>Quote</blockquote> </span></h2> + +!! end + +!! test +Unclosed tags in TOC +!! options +title=[[Main Page]] +!! input +__TOC__ +== Proof: 2 < 3 == +<small>Hanc marginis exiguitas non caperet.</small> +QED +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#Proof:_2_.3C_3"><span class="tocnumber">1</span> <span class="toctext">Proof: 2 < 3</span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Proof: 2 < 3">edit</a>]</span> <span class="mw-headline" id="Proof:_2_.3C_3"> Proof: 2 < 3 </span></h2> +<p><small>Hanc marginis exiguitas non caperet.</small> +QED +</p> +!! end + +!! test +Multiple tags in TOC +!! input +__TOC__ +== <i>Foo</i> <b>Bar</b> == + +== <i>Foo</i> <blockquote>Bar</blockquote> == +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#Foo_Bar"><span class="tocnumber">1</span> <span class="toctext"><i>Foo</i> <b>Bar</b></span></a></li> +<li class="toclevel-1 tocsection-2"><a href="#Foo_Bar_2"><span class="tocnumber">2</span> <span class="toctext"><i>Foo</i> Bar</span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Foo Bar">edit</a>]</span> <span class="mw-headline" id="Foo_Bar"> <i>Foo</i> <b>Bar</b> </span></h2> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Foo Bar">edit</a>]</span> <span class="mw-headline" id="Foo_Bar_2"> <i>Foo</i> <blockquote>Bar</blockquote> </span></h2> + +!! end + +!! test +Tags with parameters in TOC +!! input +__TOC__ +== <sup class="in-h2">Hello</sup> == + +== <sup class="a > b">Evilbye</sup> == +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#Hello"><span class="tocnumber">1</span> <span class="toctext"><sup>Hello</sup></span></a></li> +<li class="toclevel-1 tocsection-2"><a href="#b.22.3EEvilbye"><span class="tocnumber">2</span> <span class="toctext"><sup> b">Evilbye</sup></span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Hello">edit</a>]</span> <span class="mw-headline" id="Hello"> <sup class="in-h2">Hello</sup> </span></h2> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: b">Evilbye">edit</a>]</span> <span class="mw-headline" id="b.22.3EEvilbye"> <sup> b">Evilbye</sup> </span></h2> + +!! end !! article MediaWiki:Bug32057 @@ -8895,6 +9176,15 @@ Strip marker in anchorencode </p> !! end +!! test +nowiki inside link inside heading (bug 18295) +!! input +==[[foo|x<nowiki>y</nowiki>z]]== +!! result +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: xyz">edit</a>]</span> <span class="mw-headline" id="xyz"><a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)">xyz</a></span></h2> + +!! end + TODO: more images diff --git a/tests/parser/parserTestsParserHook.php b/tests/parser/parserTestsParserHook.php index 324b8e5c..24d852c5 100644 --- a/tests/parser/parserTestsParserHook.php +++ b/tests/parser/parserTestsParserHook.php @@ -29,7 +29,7 @@ class ParserTestParserHook { static function setup( &$parser ) { $parser->setHook( 'tag', array( __CLASS__, 'dumpHook' ) ); - + $parser->setHook( 'statictag', array( __CLASS__, 'staticTagHook' ) ); return true; } @@ -43,4 +43,28 @@ class ParserTestParserHook { return "<pre>\n$ret</pre>"; } + + static function staticTagHook( $in, $argv, $parser ) { + if ( ! count( $argv ) ) { + $parser->static_tag_buf = $in; + return ''; + } elseif ( count( $argv ) === 1 && isset( $argv['action'] ) + && $argv['action'] === 'flush' && $in === null ) + { + // Clear the buffer, we probably don't need to + if ( isset( $parser->static_tag_buf ) ) { + $tmp = $parser->static_tag_buf; + } else { + $tmp = ''; + } + $parser->static_tag_buf = null; + return $tmp; + } else + // wtf? + return + "\nCall this extension as <statictag>string</statictag> or as" . + " <statictag action=flush/>, not in any other way.\n" . + "text: " . var_export( $in, true ) . "\n" . + "argv: " . var_export( $argv, true ) . "\n"; + } } diff --git a/tests/parser/parserTestsStaticParserHook.php b/tests/parser/parserTestsStaticParserHook.php deleted file mode 100644 index e82f7f3f..00000000 --- a/tests/parser/parserTestsStaticParserHook.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php -/** - * A basic extension that's used by the parser tests to test whether the parser - * calls extensions when they're called inside comments, it shouldn't do that - * - * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup Testing - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - */ - -class ParserTestStaticParserHook { - static function setup( &$parser ) { - $parser->setHook( 'statictag', array( __CLASS__, 'hook' ) ); - - return true; - } - - static function hook( $in, $argv, $parser ) { - if ( ! count( $argv ) ) { - $parser->static_tag_buf = $in; - return ''; - } elseif ( count( $argv ) === 1 && isset( $argv['action'] ) - && $argv['action'] === 'flush' && $in === null ) - { - // Clear the buffer, we probably don't need to - if ( isset( $parser->static_tag_buf ) ) { - $tmp = $parser->static_tag_buf; - } else { - $tmp = ''; - } - $parser->static_tag_buf = null; - return $tmp; - } else - // wtf? - return - "\nCall this extension as <statictag>string</statictag> or as" . - " <statictag action=flush/>, not in any other way.\n" . - "text: " . var_export( $in, true ) . "\n" . - "argv: " . var_export( $argv, true ) . "\n"; - } -} diff --git a/tests/parser/preprocess/All_system_messages.expected b/tests/parser/preprocess/All_system_messages.expected index 96d4569b..897c5fb0 100644 --- a/tests/parser/preprocess/All_system_messages.expected +++ b/tests/parser/preprocess/All_system_messages.expected @@ -1,4 +1,4 @@ -<root><template lineStart="1"><title>int:allmessagestext</title></template> +<root><template><title>int:allmessagestext</title></template> <table border=1 width=100%><tr><td> '''Name''' diff --git a/tests/parser/preprocess/Factorial.expected b/tests/parser/preprocess/Factorial.expected index 099029c0..a10fd6ca 100644 --- a/tests/parser/preprocess/Factorial.expected +++ b/tests/parser/preprocess/Factorial.expected @@ -1,4 +1,4 @@ -<root><template lineStart="1"><title>#expr:<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=00</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=01</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=02</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*02<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=03</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*03<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=04</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*04<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=05</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*05<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=06</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*06<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=07</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*07<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=08</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*08<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=09</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*09<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=10</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*10<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=11</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*11<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=12</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*12<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=13</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*13<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=14</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*14<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=15</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*15<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=16</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*16<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=17</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*17<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=18</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*18<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=19</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*19<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=20</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*20<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=21</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*21<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=22</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*22<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=23</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*23<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=24</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*24<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=25</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*25<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=26</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*26<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=27</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*27<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=28</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*28<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=29</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*29<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=30</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*30<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=31</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*31<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=32</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*32<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=33</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*33<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=34</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*34<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=35</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*35<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=36</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*36<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=37</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*37<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=38</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*38<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=39</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*39<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=40</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*40<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=41</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*41<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=42</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*42<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=43</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*43<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=44</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*44<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=45</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*45<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=46</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*46<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=47</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*47<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=48</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*48<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=49</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*49<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=50</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*50<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=51</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*51<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=52</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*52<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=53</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*53<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=54</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*54<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=55</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*55<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=56</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*56<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=57</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*57<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=58</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*58<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=59</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*59<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=60</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*60<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=61</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*61<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=62</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*62<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=63</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*63<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=64</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*64<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=65</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*65<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=66</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*66<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=67</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*67<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=68</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*68<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=69</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*69<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=70</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*70<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=71</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*71<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=72</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*72<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=73</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*73<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=74</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*74<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=75</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*75<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=76</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*76<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=77</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*77<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=78</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*78<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=79</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*79<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=80</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*80<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=81</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*81<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=82</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*82<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=83</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*83<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=84</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*84<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=85</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*85<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=86</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*86<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=87</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*87<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=88</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*88<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=89</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*89<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=90</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*90<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=91</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*91<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=92</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*92<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=93</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*93<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=94</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*94<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=95</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*95<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=96</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*96<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=97</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*97<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=98</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*98<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=99</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*99</value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></title></template><ignore><noinclude></ignore> +<root><template><title>#expr:<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=00</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=01</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=02</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*02<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=03</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*03<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=04</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*04<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=05</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*05<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=06</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*06<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=07</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*07<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=08</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*08<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=09</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*09<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=10</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*10<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=11</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*11<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=12</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*12<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=13</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*13<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=14</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*14<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=15</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*15<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=16</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*16<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=17</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*17<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=18</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*18<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=19</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*19<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=20</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*20<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=21</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*21<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=22</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*22<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=23</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*23<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=24</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*24<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=25</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*25<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=26</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*26<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=27</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*27<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=28</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*28<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=29</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*29<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=30</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*30<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=31</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*31<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=32</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*32<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=33</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*33<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=34</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*34<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=35</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*35<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=36</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*36<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=37</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*37<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=38</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*38<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=39</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*39<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=40</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*40<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=41</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*41<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=42</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*42<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=43</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*43<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=44</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*44<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=45</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*45<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=46</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*46<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=47</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*47<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=48</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*48<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=49</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*49<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=50</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*50<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=51</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*51<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=52</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*52<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=53</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*53<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=54</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*54<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=55</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*55<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=56</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*56<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=57</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*57<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=58</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*58<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=59</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*59<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=60</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*60<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=61</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*61<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=62</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*62<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=63</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*63<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=64</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*64<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=65</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*65<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=66</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*66<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=67</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*67<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=68</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*68<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=69</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*69<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=70</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*70<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=71</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*71<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=72</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*72<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=73</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*73<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=74</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*74<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=75</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*75<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=76</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*76<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=77</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*77<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=78</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*78<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=79</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*79<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=80</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*80<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=81</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*81<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=82</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*82<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=83</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*83<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=84</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*84<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=85</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*85<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=86</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*86<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=87</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*87<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=88</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*88<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=89</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*89<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=90</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*90<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=91</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*91<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=92</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*92<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=93</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*93<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=94</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*94<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=95</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*95<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=96</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*96<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=97</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*97<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=98</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*98<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>>=99</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*99</value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></title></template><ignore><noinclude></ignore> <template lineStart="1"><title>Template documentation</title></template> This template finds the [[factorial]] of a number. To use it, enter:<br /> <code><nowiki><template><title>factorial</title><part><name index="1" /><value>input</value></part></template></nowiki></code><br /> diff --git a/tests/parserTests.php b/tests/parserTests.php index 99ea2ed4..d930ac5a 100644 --- a/tests/parserTests.php +++ b/tests/parserTests.php @@ -24,8 +24,8 @@ * @ingroup Testing */ -$options = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' ); -$optionsWithArgs = array( 'regex', 'seed', 'setversion' ); +$otions = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' ); +$optionsWithArgs = array( 'regex', 'filter', 'seed', 'setversion' ); require_once( dirname( __FILE__ ) . '/../maintenance/commandLine.inc' ); @@ -39,13 +39,14 @@ Options: --quiet Suppress notification of passed tests (shows only failed tests) --show-output Show expected and actual output --color[=yes|no] Override terminal detection and force color output on or off - use wgCommandLineDarkBg = true; if your term is dark + use wgCommandLineDarkBg = true; if your term is dark --regex Only run tests whose descriptions which match given regex + --filter Alias for --regex --file=<testfile> Run test cases from a custom file instead of parserTests.txt --record Record tests in database --compare Compare with recorded results, without updating the database. --setversion When using --record, set the version string to use (useful - with git-svn so that you can get the exact revision) + with git-svn so that you can get the exact revision) --keep-uploads Re-use the same upload directory for each test, don't delete it --fuzz Do a fuzz test instead of a normal test --seed <n> Start the fuzz test from the specified seed diff --git a/tests/phpunit/Makefile b/tests/phpunit/Makefile index 24536efc..8a55dae0 100644 --- a/tests/phpunit/Makefile +++ b/tests/phpunit/Makefile @@ -46,17 +46,26 @@ coverage: parser: ${PU} --group Parser +parserfuzz: + @echo "******************************************************************" + @echo "* This WILL kill your computer by eating all memory AND all swap *" + @echo "* *" + @echo "* If you are on a production machine. ABORT NOW!! *" + @echo "* Press control+C to stop *" + @echo "* *" + @echo "******************************************************************" + ${PU} --group Parser,ParserFuzz noparser: - ${PU} --exclude-group Parser,Broken,Stub + ${PU} --exclude-group Parser,Broken,ParserFuzz,Stub safe: - ${PU} --exclude-group Broken,Destructive,Stub + ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub databaseless: - ${PU} --exclude-group Broken,Destructive,Database,Stub + ${PU} --exclude-group Broken,ParserFuzz,Destructive,Database,Stub database: - ${PU} --exclude-group Broken,Destructive,Stub --group Database + ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub --group Database list-groups: ${PU} --list-groups diff --git a/tests/phpunit/MediaWikiLangTestCase.php b/tests/phpunit/MediaWikiLangTestCase.php index 1cd6a3ba..783f0315 100644 --- a/tests/phpunit/MediaWikiLangTestCase.php +++ b/tests/phpunit/MediaWikiLangTestCase.php @@ -13,7 +13,11 @@ abstract class MediaWikiLangTestCase extends MediaWikiTestCase { self::$oldLang = $wgLang; self::$oldContLang = $wgContLang; - if( $wgLanguageCode != $wgContLang->getCode() ) die("nooo!"); + if( $wgLanguageCode != $wgContLang->getCode() ) { + throw new MWException("Error in MediaWikiLangTestCase::setUp(): " . + "\$wgLanguageCode ('$wgLanguageCode') is different from " . + "\$wgContLang->getCode() (" . $wgContLang->getCode() . ")" ); + } $wgLanguageCode = 'en'; # For mainpage to be 'Main Page' diff --git a/tests/phpunit/MediaWikiPHPUnitCommand.php b/tests/phpunit/MediaWikiPHPUnitCommand.php index c0d9f363..ea385ad9 100644 --- a/tests/phpunit/MediaWikiPHPUnitCommand.php +++ b/tests/phpunit/MediaWikiPHPUnitCommand.php @@ -5,7 +5,10 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command { static $additionalOptions = array( 'regex=' => false, 'file=' => false, + 'use-filebackend=' => false, 'keep-uploads' => false, + 'use-normal-tables' => false, + 'reuse-db' => false, ); public function __construct() { @@ -17,6 +20,28 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command { public static function main( $exit = true ) { $command = new self; + + if( wfIsWindows() ) { + # Windows does not come anymore with ANSI.SYS loaded by default + # PHPUnit uses the suite.xml parameters to enable/disable colors + # which can be then forced to be enabled with --colors. + # The below code inject a parameter just like if the user called + # phpunit with a --no-color option (which does not exist). It + # overrides the suite.xml setting. + # Probably fix bug 29226 + $command->arguments['colors'] = false; + } + + # Makes MediaWiki PHPUnit directory includable so the PHPUnit will + # be able to resolve relative files inclusion such as suites/* + # PHPUnit uses stream_resolve_include_path() internally + # See bug 32022 + set_include_path( + dirname( __FILE__ ) + .PATH_SEPARATOR + . get_include_path() + ); + $command->run($_SERVER['argv'], $exit); } @@ -40,6 +65,11 @@ ParserTest-specific options: --keep-uploads Re-use the same upload directory for each test, don't delete it +Database options: + --use-normal-tables Use normal DB tables. + --reuse-db Init DB only if tables are missing and keep after finish. + + EOT; } diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index 64cb486b..6ec8bdc7 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -11,6 +11,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { protected $db; protected $oldTablePrefix; protected $useTemporaryTables = true; + protected $reuseDB = false; + protected $tablesUsed = array(); // tables with data + private static $dbSetup = false; /** @@ -22,6 +25,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { protected $supportedDBs = array( 'mysql', 'sqlite', + 'postgres', 'oracle' ); @@ -40,8 +44,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { ObjectCache::$instances[CACHE_DB] = new HashBagOStuff; if( $this->needsDB() ) { - global $wgDBprefix; + + $this->useTemporaryTables = !$this->getCliArg( 'use-normal-tables' ); + $this->reuseDB = $this->getCliArg('reuse-db'); $this->db = wfGetDB( DB_MASTER ); @@ -81,6 +87,34 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { function addDBData() {} private function addCoreDBData() { + # disabled for performance + #$this->tablesUsed[] = 'page'; + #$this->tablesUsed[] = 'revision'; + + if ( $this->db->getType() == 'oracle' ) { + + # Insert 0 user to prevent FK violations + # Anonymous user + $this->db->insert( 'user', array( + 'user_id' => 0, + 'user_name' => 'Anonymous' ), __METHOD__, array( 'IGNORE' ) ); + + # Insert 0 page to prevent FK violations + # Blank page + $this->db->insert( 'page', array( + 'page_id' => 0, + 'page_namespace' => 0, + 'page_title' => ' ', + 'page_restrictions' => NULL, + 'page_counter' => 0, + 'page_is_redirect' => 0, + 'page_is_new' => 0, + 'page_random' => 0, + 'page_touched' => $this->db->timestamp(), + 'page_latest' => 0, + 'page_len' => 0 ), __METHOD__, array( 'IGNORE' ) ); + + } User::resetIdByNameCache(); @@ -98,12 +132,14 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { //Make 1 page with 1 revision - $article = new Article( Title::newFromText( 'UTPage' ) ); - $article->doEdit( 'UTContent', + $page = WikiPage::factory( Title::newFromText( 'UTPage' ) ); + if ( !$page->getId() == 0 ) { + $page->doEdit( 'UTContent', 'UTPageSummary', EDIT_NEW, false, User::newFromName( 'UTSysop' ) ); + } } private function initDB() { @@ -112,18 +148,20 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { throw new MWException( 'Cannot run unit tests, the database prefix is already "unittest_"' ); } - $dbClone = new CloneDatabase( $this->db, $this->listTables(), $this->dbPrefix() ); + $tablesCloned = $this->listTables(); + $dbClone = new CloneDatabase( $this->db, $tablesCloned, $this->dbPrefix() ); $dbClone->useTemporaryTables( $this->useTemporaryTables ); - $dbClone->cloneTableStructure(); + + if ( ( $this->db->getType() == 'oracle' || !$this->useTemporaryTables ) && $this->reuseDB ) { + CloneDatabase::changePrefix( $this->dbPrefix() ); + $this->resetDB(); + return; + } else { + $dbClone->cloneTableStructure(); + } if ( $this->db->getType() == 'oracle' ) { $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' ); - - # Insert 0 user to prevent FK violations - # Anonymous user - $this->db->insert( 'user', array( - 'user_id' => 0, - 'user_name' => 'Anonymous' ) ); } } @@ -132,35 +170,25 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { */ private function resetDB() { if( $this->db ) { - foreach( $this->listTables() as $tbl ) { - if( $tbl == 'interwiki' || $tbl == 'user' ) continue; - $this->db->delete( $tbl, '*', __METHOD__ ); + if ( $this->db->getType() == 'oracle' ) { + if ( $this->useTemporaryTables ) { + wfGetLB()->closeAll(); + $this->db = wfGetDB( DB_MASTER ); + } else { + foreach( $this->tablesUsed as $tbl ) { + if( $tbl == 'interwiki') continue; + $this->db->query( 'TRUNCATE TABLE '.$this->db->tableName($tbl), __METHOD__ ); + } + } + } else { + foreach( $this->tablesUsed as $tbl ) { + if( $tbl == 'interwiki' || $tbl == 'user' ) continue; + $this->db->delete( $tbl, '*', __METHOD__ ); + } } } } - protected function destroyDB() { - if ( $this->useTemporaryTables || is_null( $this->db ) ) { - # Don't need to do anything - return; - } - - $tables = $this->db->listTables( $this->dbPrefix(), __METHOD__ ); - - foreach ( $tables as $table ) { - try { - $sql = $this->db->getType() == 'oracle' ? "DROP TABLE $table CASCADE CONSTRAINTS PURGE" : "DROP TABLE `$table`"; - $this->db->query( $sql, __METHOD__ ); - } catch( MWException $mwe ) {} - } - - if ( $this->db->getType() == 'oracle' ) - $this->db->query( 'BEGIN FILL_WIKI_INFO; END;', __METHOD__ ); - - CloneDatabase::changePrefix( $this->oldTablePrefix ); - } - - function __call( $func, $args ) { static $compatibility = array( 'assertInternalType' => 'assertType', @@ -235,5 +263,16 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { public static function disableInterwikis( $prefix, &$data ) { return false; } -} + /** + * Don't throw a warning if $function is deprecated and called later + * + * @param $function String + * @return null + */ + function hideDeprecated( $function ) { + wfSuppressWarnings(); + wfDeprecated( $function ); + wfRestoreWarnings(); + } +} diff --git a/tests/phpunit/StructureTest.php b/tests/phpunit/StructureTest.php new file mode 100644 index 00000000..f967c18d --- /dev/null +++ b/tests/phpunit/StructureTest.php @@ -0,0 +1,56 @@ +<?php +/** + * The tests here verify the structure of the code. This is for outright bugs, + * not just style issues. + */ + +class StructureTest extends MediaWikiTestCase { + /** + * Verify all files that appear to be tests have file names ending in + * Test. If the file names do not end in Test, they will not be run. + */ + public function testUnitTestFileNamesEndWithTest() { + if ( wfIsWindows() ) { + $this->markTestSkipped( 'This test does not work on Windows' ); + } + $rootPath = escapeshellarg( __DIR__ ); + $testClassRegex = implode( '|', array( + 'ApiFormatTestBase', + 'ApiTestCase', + 'MediaWikiLangTestCase', + 'MediaWikiTestCase', + 'PHPUnit_Framework_TestCase', + ) ); + $testClassRegex = "^class .* extends ($testClassRegex)"; + $finder = "find $rootPath -name '*.php' '!' -name '*Test.php'" . + " | xargs grep -El '$testClassRegex|function suite\('"; + + $results = null; + $exitCode = null; + exec($finder, $results, $exitCode); + + $this->assertEquals( + 0, + $exitCode, + 'Verify find/grep command succeeds.' + ); + + $results = array_filter( + $results, + array( $this, 'filterSuites' ) + ); + + $this->assertEquals( + array(), + $results, + 'Unit test file names must end with Test.' + ); + } + + /** + * Filter to remove testUnitTestFileNamesEndWithTest false positives. + */ + public function filterSuites( $filename ) { + return strpos( $filename, __DIR__ . '/suites/' ) !== 0; + } +} diff --git a/tests/phpunit/data/db/mysql/functions.sql b/tests/phpunit/data/db/mysql/functions.sql new file mode 100644 index 00000000..9e5e470f --- /dev/null +++ b/tests/phpunit/data/db/mysql/functions.sql @@ -0,0 +1,12 @@ +-- MySQL test file for DatabaseTest::testStoredFunctions() + +DELIMITER // + +CREATE FUNCTION mw_test_function() +RETURNS int DETERMINISTIC +BEGIN + SET @foo = 21; + RETURN @foo * 2; +END// + +DELIMITER // diff --git a/tests/phpunit/data/db/postgres/functions.sql b/tests/phpunit/data/db/postgres/functions.sql new file mode 100644 index 00000000..3086d4d5 --- /dev/null +++ b/tests/phpunit/data/db/postgres/functions.sql @@ -0,0 +1,12 @@ +-- Postgres test file for DatabaseTest::testStoredFunctions() + +CREATE FUNCTION mw_test_function() +RETURNS INTEGER +LANGUAGE plpgsql AS +$mw$ +DECLARE foo INTEGER; +BEGIN + foo := 21; + RETURN foo * 2; +END +$mw$; diff --git a/tests/phpunit/includes/db/sqlite/tables-1.13.sql b/tests/phpunit/data/db/sqlite/tables-1.13.sql index a0dcb553..66847ab1 100644 --- a/tests/phpunit/includes/db/sqlite/tables-1.13.sql +++ b/tests/phpunit/data/db/sqlite/tables-1.13.sql @@ -123,7 +123,7 @@ CREATE TABLE /*$wgDBprefix*/site_stats ( ss_images INTEGER default '0') /*$wgDBTableOptions*/; CREATE TABLE /*$wgDBprefix*/hitcounter ( - hc_id INTEGER + hc_id INTEGER ) ; CREATE TABLE /*$wgDBprefix*/ipblocks ( diff --git a/tests/phpunit/includes/db/sqlite/tables-1.15.sql b/tests/phpunit/data/db/sqlite/tables-1.15.sql index 901bac52..6b3a628e 100644 --- a/tests/phpunit/includes/db/sqlite/tables-1.15.sql +++ b/tests/phpunit/data/db/sqlite/tables-1.15.sql @@ -141,7 +141,7 @@ CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from); CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60)); CREATE TABLE /*_*/langlinks ( ll_from int unsigned NOT NULL default 0, - + ll_lang varbinary(20) NOT NULL default '', ll_title varchar(255) binary NOT NULL default '' ) /*$wgDBTableOptions*/; @@ -181,7 +181,7 @@ CREATE TABLE /*_*/ipblocks ( ipb_block_email bool NOT NULL default 0, ipb_allow_usertalk bool NOT NULL default 0 ) /*$wgDBTableOptions*/; - + CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only); CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user); CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8)); diff --git a/tests/phpunit/includes/db/sqlite/tables-1.16.sql b/tests/phpunit/data/db/sqlite/tables-1.16.sql index 6e56add2..6e56add2 100644 --- a/tests/phpunit/includes/db/sqlite/tables-1.16.sql +++ b/tests/phpunit/data/db/sqlite/tables-1.16.sql diff --git a/tests/phpunit/includes/db/sqlite/tables-1.17.sql b/tests/phpunit/data/db/sqlite/tables-1.17.sql index 69ae3764..69ae3764 100644 --- a/tests/phpunit/includes/db/sqlite/tables-1.17.sql +++ b/tests/phpunit/data/db/sqlite/tables-1.17.sql diff --git a/tests/phpunit/data/db/sqlite/tables-1.18.sql b/tests/phpunit/data/db/sqlite/tables-1.18.sql new file mode 100644 index 00000000..bedf6c33 --- /dev/null +++ b/tests/phpunit/data/db/sqlite/tables-1.18.sql @@ -0,0 +1,535 @@ +-- This is a copy of MediaWiki 1.18 schema shared by MySQL and SQLite. +-- It is used for updater testing. Comments are stripped to decrease +-- file size, as we don't need to maintain it. + +CREATE TABLE /*_*/user ( + user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, + user_name varchar(255) binary NOT NULL default '', + user_real_name varchar(255) binary NOT NULL default '', + user_password tinyblob NOT NULL, + user_newpassword tinyblob NOT NULL, + user_newpass_time binary(14), + user_email tinytext NOT NULL, + user_options blob NOT NULL, + user_touched binary(14) NOT NULL default '', + user_token binary(32) NOT NULL default '', + user_email_authenticated binary(14), + user_email_token binary(32), + user_email_token_expires binary(14), + user_registration binary(14), + user_editcount int +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name); +CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token); +CREATE INDEX /*i*/user_email ON /*_*/user (user_email(50)); +CREATE TABLE /*_*/user_groups ( + ug_user int unsigned NOT NULL default 0, + ug_group varbinary(16) NOT NULL default '' +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group); +CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group); +CREATE TABLE /*_*/user_former_groups ( + ufg_user int unsigned NOT NULL default 0, + ufg_group varbinary(16) NOT NULL default '' +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group); +CREATE TABLE /*_*/user_newtalk ( + user_id int NOT NULL default 0, + user_ip varbinary(40) NOT NULL default '', + user_last_timestamp varbinary(14) NULL default NULL +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id); +CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip); +CREATE TABLE /*_*/user_properties ( + up_user int NOT NULL, + up_property varbinary(255) NOT NULL, + up_value blob +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property); +CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property); +CREATE TABLE /*_*/page ( + page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, + page_namespace int NOT NULL, + page_title varchar(255) binary NOT NULL, + page_restrictions tinyblob NOT NULL, + page_counter bigint unsigned NOT NULL default 0, + page_is_redirect tinyint unsigned NOT NULL default 0, + page_is_new tinyint unsigned NOT NULL default 0, + page_random real unsigned NOT NULL, + page_touched binary(14) NOT NULL default '', + page_latest int unsigned NOT NULL, + page_len int unsigned NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title); +CREATE INDEX /*i*/page_random ON /*_*/page (page_random); +CREATE INDEX /*i*/page_len ON /*_*/page (page_len); +CREATE TABLE /*_*/revision ( + rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, + rev_page int unsigned NOT NULL, + rev_text_id int unsigned NOT NULL, + rev_comment tinyblob NOT NULL, + rev_user int unsigned NOT NULL default 0, + rev_user_text varchar(255) binary NOT NULL default '', + rev_timestamp binary(14) NOT NULL default '', + rev_minor_edit tinyint unsigned NOT NULL default 0, + rev_deleted tinyint unsigned NOT NULL default 0, + rev_len int unsigned, + rev_parent_id int unsigned default NULL +) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024; +CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id); +CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp); +CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp); +CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp); +CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp); +CREATE TABLE /*_*/text ( + old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, + old_text mediumblob NOT NULL, + old_flags tinyblob NOT NULL +) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240; +CREATE TABLE /*_*/archive ( + ar_namespace int NOT NULL default 0, + ar_title varchar(255) binary NOT NULL default '', + ar_text mediumblob NOT NULL, + ar_comment tinyblob NOT NULL, + ar_user int unsigned NOT NULL default 0, + ar_user_text varchar(255) binary NOT NULL, + ar_timestamp binary(14) NOT NULL default '', + ar_minor_edit tinyint NOT NULL default 0, + ar_flags tinyblob NOT NULL, + ar_rev_id int unsigned, + ar_text_id int unsigned, + ar_deleted tinyint unsigned NOT NULL default 0, + ar_len int unsigned, + ar_page_id int unsigned, + ar_parent_id int unsigned default NULL +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp); +CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp); +CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id); +CREATE TABLE /*_*/pagelinks ( + pl_from int unsigned NOT NULL default 0, + pl_namespace int NOT NULL default 0, + pl_title varchar(255) binary NOT NULL default '' +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title); +CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from); +CREATE TABLE /*_*/templatelinks ( + tl_from int unsigned NOT NULL default 0, + tl_namespace int NOT NULL default 0, + tl_title varchar(255) binary NOT NULL default '' +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title); +CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from); +CREATE TABLE /*_*/imagelinks ( + il_from int unsigned NOT NULL default 0, + il_to varchar(255) binary NOT NULL default '' +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to); +CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from); +CREATE TABLE /*_*/categorylinks ( + cl_from int unsigned NOT NULL default 0, + cl_to varchar(255) binary NOT NULL default '', + cl_sortkey varbinary(230) NOT NULL default '', + cl_sortkey_prefix varchar(255) binary NOT NULL default '', + cl_timestamp timestamp NOT NULL, + cl_collation varbinary(32) NOT NULL default '', + cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page' +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to); +CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from); +CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp); +CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation); +CREATE TABLE /*_*/category ( + cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, + cat_title varchar(255) binary NOT NULL, + cat_pages int signed NOT NULL default 0, + cat_subcats int signed NOT NULL default 0, + cat_files int signed NOT NULL default 0, + cat_hidden tinyint unsigned NOT NULL default 0 +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title); +CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages); +CREATE TABLE /*_*/externallinks ( + el_from int unsigned NOT NULL default 0, + el_to blob NOT NULL, + el_index blob NOT NULL +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40)); +CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from); +CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60)); +CREATE TABLE /*_*/external_user ( + eu_local_id int unsigned NOT NULL PRIMARY KEY, + eu_external_id varchar(255) binary NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id); +CREATE TABLE /*_*/langlinks ( + ll_from int unsigned NOT NULL default 0, + ll_lang varbinary(20) NOT NULL default '', + ll_title varchar(255) binary NOT NULL default '' +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang); +CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title); +CREATE TABLE /*_*/iwlinks ( + iwl_from int unsigned NOT NULL default 0, + iwl_prefix varbinary(20) NOT NULL default '', + iwl_title varchar(255) binary NOT NULL default '' +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title); +CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from); +CREATE TABLE /*_*/site_stats ( + ss_row_id int unsigned NOT NULL, + ss_total_views bigint unsigned default 0, + ss_total_edits bigint unsigned default 0, + ss_good_articles bigint unsigned default 0, + ss_total_pages bigint default '-1', + ss_users bigint default '-1', + ss_active_users bigint default '-1', + ss_admins int default '-1', + ss_images int default 0 +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id); +CREATE TABLE /*_*/hitcounter ( + hc_id int unsigned NOT NULL +) ENGINE=HEAP MAX_ROWS=25000; +CREATE TABLE /*_*/ipblocks ( + ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT, + ipb_address tinyblob NOT NULL, + ipb_user int unsigned NOT NULL default 0, + ipb_by int unsigned NOT NULL default 0, + ipb_by_text varchar(255) binary NOT NULL default '', + ipb_reason tinyblob NOT NULL, + ipb_timestamp binary(14) NOT NULL default '', + ipb_auto bool NOT NULL default 0, + ipb_anon_only bool NOT NULL default 0, + ipb_create_account bool NOT NULL default 1, + ipb_enable_autoblock bool NOT NULL default '1', + ipb_expiry varbinary(14) NOT NULL default '', + ipb_range_start tinyblob NOT NULL, + ipb_range_end tinyblob NOT NULL, + ipb_deleted bool NOT NULL default 0, + ipb_block_email bool NOT NULL default 0, + ipb_allow_usertalk bool NOT NULL default 0 +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only); +CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user); +CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8)); +CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp); +CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry); +CREATE TABLE /*_*/image ( + img_name varchar(255) binary NOT NULL default '' PRIMARY KEY, + img_size int unsigned NOT NULL default 0, + img_width int NOT NULL default 0, + img_height int NOT NULL default 0, + img_metadata mediumblob NOT NULL, + img_bits int NOT NULL default 0, + img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, + img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown", + img_minor_mime varbinary(100) NOT NULL default "unknown", + img_description tinyblob NOT NULL, + img_user int unsigned NOT NULL default 0, + img_user_text varchar(255) binary NOT NULL, + img_timestamp varbinary(14) NOT NULL default '', + img_sha1 varbinary(32) NOT NULL default '' +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp); +CREATE INDEX /*i*/img_size ON /*_*/image (img_size); +CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp); +CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1); +CREATE TABLE /*_*/oldimage ( + oi_name varchar(255) binary NOT NULL default '', + oi_archive_name varchar(255) binary NOT NULL default '', + oi_size int unsigned NOT NULL default 0, + oi_width int NOT NULL default 0, + oi_height int NOT NULL default 0, + oi_bits int NOT NULL default 0, + oi_description tinyblob NOT NULL, + oi_user int unsigned NOT NULL default 0, + oi_user_text varchar(255) binary NOT NULL, + oi_timestamp binary(14) NOT NULL default '', + oi_metadata mediumblob NOT NULL, + oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, + oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown", + oi_minor_mime varbinary(100) NOT NULL default "unknown", + oi_deleted tinyint unsigned NOT NULL default 0, + oi_sha1 varbinary(32) NOT NULL default '' +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp); +CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp); +CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14)); +CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1); +CREATE TABLE /*_*/filearchive ( + fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT, + fa_name varchar(255) binary NOT NULL default '', + fa_archive_name varchar(255) binary default '', + fa_storage_group varbinary(16), + fa_storage_key varbinary(64) default '', + fa_deleted_user int, + fa_deleted_timestamp binary(14) default '', + fa_deleted_reason text, + fa_size int unsigned default 0, + fa_width int default 0, + fa_height int default 0, + fa_metadata mediumblob, + fa_bits int default 0, + fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, + fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown", + fa_minor_mime varbinary(100) default "unknown", + fa_description tinyblob, + fa_user int unsigned default 0, + fa_user_text varchar(255) binary, + fa_timestamp binary(14) default '', + fa_deleted tinyint unsigned NOT NULL default 0 +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp); +CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key); +CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp); +CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp); +CREATE TABLE /*_*/uploadstash ( + us_id int unsigned NOT NULL PRIMARY KEY auto_increment, + us_user int unsigned NOT NULL, + us_key varchar(255) NOT NULL, + us_orig_path varchar(255) NOT NULL, + us_path varchar(255) NOT NULL, + us_source_type varchar(50), + us_timestamp varbinary(14) not null, + us_status varchar(50) not null, + us_size int unsigned NOT NULL, + us_sha1 varchar(31) NOT NULL, + us_mime varchar(255), + us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, + us_image_width int unsigned, + us_image_height int unsigned, + us_image_bits smallint unsigned +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user); +CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key); +CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp); +CREATE TABLE /*_*/recentchanges ( + rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT, + rc_timestamp varbinary(14) NOT NULL default '', + rc_cur_time varbinary(14) NOT NULL default '', + rc_user int unsigned NOT NULL default 0, + rc_user_text varchar(255) binary NOT NULL, + rc_namespace int NOT NULL default 0, + rc_title varchar(255) binary NOT NULL default '', + rc_comment varchar(255) binary NOT NULL default '', + rc_minor tinyint unsigned NOT NULL default 0, + rc_bot tinyint unsigned NOT NULL default 0, + rc_new tinyint unsigned NOT NULL default 0, + rc_cur_id int unsigned NOT NULL default 0, + rc_this_oldid int unsigned NOT NULL default 0, + rc_last_oldid int unsigned NOT NULL default 0, + rc_type tinyint unsigned NOT NULL default 0, + rc_moved_to_ns tinyint unsigned NOT NULL default 0, + rc_moved_to_title varchar(255) binary NOT NULL default '', + rc_patrolled tinyint unsigned NOT NULL default 0, + rc_ip varbinary(40) NOT NULL default '', + rc_old_len int, + rc_new_len int, + rc_deleted tinyint unsigned NOT NULL default 0, + rc_logid int unsigned NOT NULL default 0, + rc_log_type varbinary(255) NULL default NULL, + rc_log_action varbinary(255) NULL default NULL, + rc_params blob NULL +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp); +CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title); +CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id); +CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp); +CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip); +CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text); +CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp); +CREATE TABLE /*_*/watchlist ( + wl_user int unsigned NOT NULL, + wl_namespace int NOT NULL default 0, + wl_title varchar(255) binary NOT NULL default '', + wl_notificationtimestamp varbinary(14) +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title); +CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title); +CREATE TABLE /*_*/searchindex ( + si_page int unsigned NOT NULL, + si_title varchar(255) NOT NULL default '', + si_text mediumtext NOT NULL +) ENGINE=MyISAM; +CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page); +CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title); +CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text); +CREATE TABLE /*_*/interwiki ( + iw_prefix varchar(32) NOT NULL, + iw_url blob NOT NULL, + iw_api blob NOT NULL, + iw_wikiid varchar(64) NOT NULL, + iw_local bool NOT NULL, + iw_trans tinyint NOT NULL default 0 +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix); +CREATE TABLE /*_*/querycache ( + qc_type varbinary(32) NOT NULL, + qc_value int unsigned NOT NULL default 0, + qc_namespace int NOT NULL default 0, + qc_title varchar(255) binary NOT NULL default '' +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value); +CREATE TABLE /*_*/objectcache ( + keyname varbinary(255) NOT NULL default '' PRIMARY KEY, + value mediumblob, + exptime datetime +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime); +CREATE TABLE /*_*/transcache ( + tc_url varbinary(255) NOT NULL, + tc_contents text, + tc_time binary(14) NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url); +CREATE TABLE /*_*/logging ( + log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, + log_type varbinary(32) NOT NULL default '', + log_action varbinary(32) NOT NULL default '', + log_timestamp binary(14) NOT NULL default '19700101000000', + log_user int unsigned NOT NULL default 0, + log_user_text varchar(255) binary NOT NULL default '', + log_namespace int NOT NULL default 0, + log_title varchar(255) binary NOT NULL default '', + log_page int unsigned NULL, + log_comment varchar(255) NOT NULL default '', + log_params blob NOT NULL, + log_deleted tinyint unsigned NOT NULL default 0 +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp); +CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp); +CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp); +CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp); +CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp); +CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp); +CREATE TABLE /*_*/log_search ( + ls_field varbinary(32) NOT NULL, + ls_value varchar(255) NOT NULL, + ls_log_id int unsigned NOT NULL default 0 +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id); +CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id); +CREATE TABLE /*_*/trackbacks ( + tb_id int PRIMARY KEY AUTO_INCREMENT, + tb_page int REFERENCES /*_*/page(page_id) ON DELETE CASCADE, + tb_title varchar(255) NOT NULL, + tb_url blob NOT NULL, + tb_ex text, + tb_name varchar(255) +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/tb_page ON /*_*/trackbacks (tb_page); +CREATE TABLE /*_*/job ( + job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, + job_cmd varbinary(60) NOT NULL default '', + job_namespace int NOT NULL, + job_title varchar(255) binary NOT NULL, + job_params blob NOT NULL +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128)); +CREATE TABLE /*_*/querycache_info ( + qci_type varbinary(32) NOT NULL default '', + qci_timestamp binary(14) NOT NULL default '19700101000000' +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type); +CREATE TABLE /*_*/redirect ( + rd_from int unsigned NOT NULL default 0 PRIMARY KEY, + rd_namespace int NOT NULL default 0, + rd_title varchar(255) binary NOT NULL default '', + rd_interwiki varchar(32) default NULL, + rd_fragment varchar(255) binary default NULL +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from); +CREATE TABLE /*_*/querycachetwo ( + qcc_type varbinary(32) NOT NULL, + qcc_value int unsigned NOT NULL default 0, + qcc_namespace int NOT NULL default 0, + qcc_title varchar(255) binary NOT NULL default '', + qcc_namespacetwo int NOT NULL default 0, + qcc_titletwo varchar(255) binary NOT NULL default '' +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value); +CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title); +CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo); +CREATE TABLE /*_*/page_restrictions ( + pr_page int NOT NULL, + pr_type varbinary(60) NOT NULL, + pr_level varbinary(60) NOT NULL, + pr_cascade tinyint NOT NULL, + pr_user int NULL, + pr_expiry varbinary(14) NULL, + pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type); +CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level); +CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level); +CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade); +CREATE TABLE /*_*/protected_titles ( + pt_namespace int NOT NULL, + pt_title varchar(255) binary NOT NULL, + pt_user int unsigned NOT NULL, + pt_reason tinyblob, + pt_timestamp binary(14) NOT NULL, + pt_expiry varbinary(14) NOT NULL default '', + pt_create_perm varbinary(60) NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title); +CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp); +CREATE TABLE /*_*/page_props ( + pp_page int NOT NULL, + pp_propname varbinary(60) NOT NULL, + pp_value blob NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname); +CREATE TABLE /*_*/updatelog ( + ul_key varchar(255) NOT NULL PRIMARY KEY, + ul_value blob +) /*$wgDBTableOptions*/; +CREATE TABLE /*_*/change_tag ( + ct_rc_id int NULL, + ct_log_id int NULL, + ct_rev_id int NULL, + ct_tag varchar(255) NOT NULL, + ct_params blob NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag); +CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag); +CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag); +CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id); +CREATE TABLE /*_*/tag_summary ( + ts_rc_id int NULL, + ts_log_id int NULL, + ts_rev_id int NULL, + ts_tags blob NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id); +CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id); +CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id); +CREATE TABLE /*_*/valid_tag ( + vt_tag varchar(255) NOT NULL PRIMARY KEY +) /*$wgDBTableOptions*/; +CREATE TABLE /*_*/l10n_cache ( + lc_lang varbinary(32) NOT NULL, + lc_key varchar(255) NOT NULL, + lc_value mediumblob NOT NULL +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key); +CREATE TABLE /*_*/msg_resource ( + mr_resource varbinary(255) NOT NULL, + mr_lang varbinary(32) NOT NULL, + mr_blob mediumblob NOT NULL, + mr_timestamp binary(14) NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang); +CREATE TABLE /*_*/msg_resource_links ( + mrl_resource varbinary(255) NOT NULL, + mrl_message varbinary(255) NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource); +CREATE TABLE /*_*/module_deps ( + md_module varbinary(255) NOT NULL, + md_skin varbinary(32) NOT NULL, + md_deps mediumblob NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin); + diff --git a/tests/phpunit/data/media/80x60-2layers.xcf b/tests/phpunit/data/media/80x60-2layers.xcf Binary files differnew file mode 100644 index 00000000..c51e980c --- /dev/null +++ b/tests/phpunit/data/media/80x60-2layers.xcf diff --git a/tests/phpunit/data/media/80x60-Greyscale.xcf b/tests/phpunit/data/media/80x60-Greyscale.xcf Binary files differnew file mode 100644 index 00000000..84bf3e67 --- /dev/null +++ b/tests/phpunit/data/media/80x60-Greyscale.xcf diff --git a/tests/phpunit/data/media/80x60-RGB.xcf b/tests/phpunit/data/media/80x60-RGB.xcf Binary files differnew file mode 100644 index 00000000..1d58f16d --- /dev/null +++ b/tests/phpunit/data/media/80x60-RGB.xcf diff --git a/tests/phpunit/data/media/Toll_Texas_1.svg b/tests/phpunit/data/media/Toll_Texas_1.svg new file mode 100644 index 00000000..73004e3e --- /dev/null +++ b/tests/phpunit/data/media/Toll_Texas_1.svg @@ -0,0 +1,150 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY ns_svg "http://www.w3.org/2000/svg"> + <!ENTITY ns_xlink "http://www.w3.org/1999/xlink"> +]> +<svg version="1.1" id="Layer_1" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="385" height="385.0004883" + viewBox="0 0 385 385.0004883" overflow="visible" enable-background="new 0 0 385 385.0004883" xml:space="preserve"> +<g> + <g> + <g> + <path fill="#FFFFFF" d="M0.5,24.5c0-13.2548828,10.7451172-24,24-24h336c13.2548828,0,24,10.7451172,24,24v336.0004883 + c0,13.2548828-10.7451172,24-24,24h-336c-13.2548828,0-24-10.7451172-24-24V24.5L0.5,24.5z"/> + <path fill="#FFFFFF" d="M192.5,192.5004883"/> + </g> + <g> + <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" d="M0.5,24.5 + c0-13.2548828,10.7451172-24,24-24h336c13.2548828,0,24,10.7451172,24,24v336.0004883c0,13.2548828-10.7451172,24-24,24h-336 + c-13.2548828,0-24-10.7451172-24-24V24.5L0.5,24.5z"/> + <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" d=" + M192.5,192.5004883"/> + </g> + </g> + <g> + <path fill="#003882" d="M24.5,0.5h336c13.2548828,0,24,10.7451172,24,24v232.0004883H0.5V24.5 + C0.5,11.2451172,11.2451172,0.5,24.5,0.5z"/> + </g> + <g> + <path fill="#FFFFFF" d="M10.5,24.5c0-7.7319336,6.2680664-14,14-14h336c7.7324219,0,14,6.2680664,14,14v222.0004883h-364V24.5z"/> + </g> + <g> + <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="93.809082,348.2397461 91.6787109,347.8666992 + 89.5478516,347.7368164 85.2929688,347.7368164 83.1640625,347.8666992 78.9042969,348.6157227 76.7763672,349.1166992 + 72.7666016,350.3706055 70.7631836,351.246582 68.8837891,352.121582 67.0053711,353.1254883 65.1254883,354.253418 + 63.3740234,355.5063477 60.1210938,358.2602539 58.4926758,359.762207 57.1132813,361.2641602 55.7338867,362.8959961 + 54.3603516,364.6459961 21.2949219,301.3999023 21.168457,301.3999023 22.5478516,299.6469727 23.9248047,298.0200195 + 25.3022461,296.5180664 26.9296875,295.0141602 30.1875,292.2592773 31.9404297,291.0073242 33.8188477,289.8793945 + 35.6972656,288.8774414 37.5761719,288.0004883 39.5791016,287.1245117 43.5878906,285.8706055 45.7148438,285.3706055 + 49.9755859,284.6176758 52.1020508,284.4946289 56.3632813,284.4946289 58.4926758,284.6176758 60.6201172,284.9946289 + 60.6201172,284.8696289 "/> + </g> + <g> + <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" points="32.8154297,319.559082 45.0898438,312.4233398 + 42.2080078,298.5209961 52.7299805,308.0375977 65.1254883,300.9008789 59.2421875,313.9243164 69.8867188,323.4428711 + 55.7338867,321.9389648 49.8476563,334.965332 46.9677734,321.0629883 "/> + </g> + <g> + <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#B01C2E" points="132.0053711,306.6606445 148.5385742,338.2211914 + 147.4101563,339.7241211 146.1577148,341.2270508 144.9052734,342.6020508 143.5302734,343.9848633 142.1494141,345.2358398 + 140.6484375,346.4868164 139.2705078,347.6157227 137.765625,348.6157227 136.1381836,349.6176758 134.6357422,350.621582 + 133.0083008,351.3696289 131.3798828,352.246582 129.625,352.8745117 128,353.5024414 126.2441406,354.0004883 + 124.4921875,354.5043945 122.737793,354.8774414 120.9853516,355.1293945 117.4785156,355.3793945 113.9707031,355.3793945 + 112.09375,355.2543945 110.3408203,355.003418 108.5854492,354.6274414 106.9589844,354.253418 103.4501953,353.2485352 + 101.8232422,352.6254883 100.0688477,351.871582 98.4428711,351.1235352 96.9389648,350.1176758 95.3095703,349.2426758 + 93.809082,348.2397461 93.809082,348.1147461 93.809082,348.2397461 77.1523438,316.4282227 77.2753906,316.4282227 + 77.2753906,316.5551758 78.78125,317.5581055 80.4057617,318.4350586 81.9116211,319.4360352 83.5395508,320.1860352 + 85.2929688,320.9399414 86.9208984,321.5649414 90.4257813,322.5668945 92.0551758,322.9418945 93.809082,323.3188477 + 95.5620117,323.5688477 97.4423828,323.6948242 100.9462891,323.6948242 102.6992188,323.5688477 104.4521484,323.4428711 + 106.2060547,323.1928711 107.9609375,322.8168945 111.4682617,321.8168945 113.0947266,321.1889648 114.8481445,320.5639648 + 116.4765625,319.6879883 118.1035156,318.9350586 119.6083984,317.934082 121.2363281,316.9301758 122.737793,315.9282227 + 124.1152344,314.8012695 125.6196289,313.5483398 126.9960938,312.2983398 128.3759766,310.9194336 129.625,309.5424805 + 130.8789063,308.0375977 132.0053711,306.5366211 132.1328125,306.5366211 "/> + </g> + <g> + + <polyline fill="none" stroke="#003882" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" points=" + 60.7441406,285.1196289 63,286.4956055 65.2529297,287.7485352 67.5068359,288.8774414 69.8867188,289.8793945 + 72.3916016,290.6313477 74.8955078,291.3813477 77.4008789,291.7583008 80.0302734,292.1333008 82.5366211,292.2592773 + 85.1655273,292.2592773 87.6708984,292.0083008 90.3022461,291.6313477 92.8066406,291.1313477 95.3095703,290.3793945 + 97.6904297,289.5024414 100.0688477,288.5004883 102.3256836,287.2504883 104.578125,285.8706055 106.7070313,284.4946289 + 108.7124023,282.8637695 110.5888672,281.1108398 112.3427734,279.2329102 114.0986328,277.2290039 115.5976563,275.1010742 "/> + </g> + <g> + + <line fill="none" stroke="#003882" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" x1="115.4746094" y1="275.1010742" x2="131.8793945" y2="306.2836914"/> + </g> + <g> + <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="215.1650391,349.1166992 213.1601563,348.2397461 + 211.2832031,347.237793 207.7773438,344.7329102 206.1503906,343.2319336 204.6435547,341.7270508 203.2685547,340.0991211 + 202.0166016,338.347168 200.8867188,336.5942383 199.8857422,334.590332 199.0097656,332.7114258 198.2578125,330.7075195 + 197.6328125,328.5776367 197.1289063,326.5756836 196.7548828,324.4458008 196.6318359,322.3168945 196.6318359,320.0629883 + 196.7548828,317.934082 197.0058594,315.8041992 197.3818359,313.6743164 198.6357422,309.6665039 199.5107422,307.6635742 + 200.5126953,305.7827148 201.6386719,303.9067383 202.890625,302.152832 204.2685547,300.3989258 205.6464844,298.8969727 + 207.2744141,297.3920898 208.9023438,296.0161133 210.6572266,294.887207 212.5361328,293.7602539 214.4130859,292.7602539 + 216.4179688,291.8833008 218.5449219,291.0073242 220.6767578,290.2543945 222.9306641,289.6274414 227.4384766,288.8774414 + 229.6933594,288.6254883 231.9482422,288.5004883 234.2011719,288.5004883 236.4541016,288.6254883 238.7119141,288.8774414 + 240.9638672,289.253418 243.21875,289.7543945 245.3476563,290.3793945 247.6025391,291.1313477 249.6054688,292.0083008 + 251.7363281,293.0083008 251.7363281,292.8852539 253.6132813,294.137207 255.4931641,295.5151367 257.2460938,297.0161133 + 258.8740234,298.6459961 260.5019531,300.3989258 261.8808594,302.277832 263.1328125,304.1577148 264.2578125,306.2836914 + 266.0117188,310.543457 266.6386719,312.7983398 267.390625,317.3051758 267.5136719,319.6879883 267.390625,321.9418945 + 267.265625,324.3188477 266.2626953,328.8286133 265.5117188,330.9575195 264.6347656,333.2114258 263.6318359,335.2163086 + 262.5058594,337.2192383 261.1298828,339.0981445 259.625,340.9770508 258.1240234,342.6020508 256.3701172,344.1079102 + 254.4902344,345.6098633 252.6142578,346.8618164 250.609375,347.9926758 248.4785156,348.9926758 246.3496094,349.8696289 + 244.2216797,350.621582 242.0927734,351.246582 239.8359375,351.7485352 237.5830078,352.121582 235.3291016,352.3754883 + 232.9501953,352.4985352 230.6933594,352.4985352 228.4414063,352.3754883 226.1865234,352.121582 223.9296875,351.7485352 + 221.6777344,351.246582 219.421875,350.746582 217.2949219,349.9946289 "/> + </g> + <g> + <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" points="219.9238281,303.6547852 221.9287109,301.5258789 + 224.4335938,299.8999023 227.1904297,298.8969727 230.1943359,298.2700195 233.0742188,298.3959961 235.9550781,299.0219727 + 238.7119141,300.2739258 241.0898438,301.9018555 243.0957031,304.1577148 244.5957031,306.5366211 245.9746094,308.9145508 + 246.9765625,311.4204102 247.8535156,314.0512695 248.4785156,316.8051758 248.8535156,319.559082 248.9804688,322.3168945 + 248.9804688,325.0727539 248.6035156,327.8256836 248.1035156,330.5805664 247.3496094,333.2114258 246.3496094,335.7172852 + 244.9726563,337.8442383 243.34375,339.6000977 241.3408203,341.1020508 239.2109375,342.355957 236.8330078,343.2319336 + 234.4511719,343.6049805 231.9482422,343.7319336 229.4414063,343.3579102 227.1904297,342.6020508 224.9326172,341.4770508 + 222.9306641,340.0991211 221.1757813,338.347168 219.6748047,336.3422852 218.5449219,334.2114258 217.7949219,331.8334961 + 217.0449219,329.7036133 216.0419922,325.4477539 215.7910156,323.1928711 215.6660156,320.9399414 215.6660156,318.684082 + 215.9169922,316.4282227 216.1669922,314.300293 216.6679688,312.0454102 217.2949219,309.918457 218.0458984,307.7895508 + 218.9208984,305.7827148 219.9238281,303.7817383 "/> + </g> + <g> + <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="150.4169922,290.0063477 196.3789063,289.7543945 + 192.7470703,304.4067383 192.6220703,304.5307617 192.1210938,303.7817383 191.4960938,303.1567383 190.8681641,302.5288086 + 190.1162109,302.027832 189.2421875,301.652832 188.4887695,301.2768555 187.6113281,301.0258789 186.7353516,300.7758789 + 179.9741211,300.7758789 179.8481445,345.1098633 179.8481445,345.2358398 180.0966797,346.1118164 180.3486328,347.1157227 + 180.7246094,347.9926758 181.1005859,348.7407227 181.6015625,349.6176758 182.8549805,351.1235352 183.6054688,351.7485352 + 158.5556641,351.7485352 158.5556641,351.871582 159.3095703,351.246582 160.0595703,350.4956055 160.6845703,349.7426758 + 161.1875,348.9926758 161.6879883,347.9926758 161.9384766,347.1157227 162.1894531,346.1118164 162.3144531,345.1098633 + 162.4375,300.9008789 156.5527344,300.9008789 155.1767578,301.0258789 153.9248047,301.2768555 152.671875,301.5258789 + 151.4204102,301.9018555 150.1660156,302.4008789 148.9135742,303.0297852 146.6601563,304.2797852 146.6601563,304.4067383 "/> + </g> + <g> + <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="275.7822266,344.3598633 276.03125,298.1450195 + 275.90625,297.769043 275.90625,297.894043 275.7822266,296.7661133 275.5302734,295.7670898 275.15625,294.6391602 + 274.7792969,293.637207 272.8984375,291.0073242 272.0234375,290.2543945 272.1484375,290.2543945 297.4492188,290.1313477 + 296.4453125,290.8803711 295.5683594,291.6313477 294.6904297,292.5083008 294.0673828,293.5102539 293.5644531,294.6391602 + 293.1904297,295.7670898 293.0644531,297.0161133 293.0644531,298.2700195 293.0644531,298.1450195 293.1904297,298.1450195 + 293.0644531,340.7260742 292.9394531,340.7260742 294.8183594,341.3540039 296.8222656,341.8540039 298.7011719,342.1040039 + 300.7050781,342.355957 302.7070313,342.4799805 304.5878906,342.355957 306.5917969,342.2299805 308.46875,341.8540039 + 311.4746094,340.7260742 312.4765625,340.2231445 313.3535156,339.7241211 314.3535156,339.2241211 316.109375,337.972168 + 311.8505859,351.621582 271.3984375,351.7485352 272.3994141,351.1235352 273.2753906,350.3706055 274.0292969,349.6176758 + 275.2802734,347.6157227 275.53125,346.4868164 275.7822266,345.4858398 275.90625,344.2348633 "/> + </g> + <g> + <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="327.5058594,344.3598633 327.7539063,298.1450195 + 327.6308594,297.769043 327.6308594,297.894043 327.5058594,296.7661133 327.2539063,295.7670898 326.8769531,294.6391602 + 326.5029297,293.637207 324.6259766,291.0073242 323.7480469,290.2543945 323.8740234,290.2543945 349.171875,290.1313477 + 348.1708984,290.8803711 347.2929688,291.6313477 346.4179688,292.5083008 345.7890625,293.5102539 345.2871094,294.6391602 + 344.9121094,295.7670898 344.7890625,297.0161133 344.7890625,298.2700195 344.7890625,298.1450195 344.9121094,298.1450195 + 344.7890625,340.7260742 344.6640625,340.7260742 346.5410156,341.3540039 348.5458984,341.8540039 350.4238281,342.1040039 + 352.4277344,342.355957 354.4316406,342.4799805 356.3105469,342.355957 358.3144531,342.2299805 360.1933594,341.8540039 + 361.1933594,341.4770508 363.1992188,340.7260742 364.2011719,340.2231445 365.078125,339.7241211 366.0820313,339.2241211 + 367.8320313,337.972168 363.5751953,351.621582 323.1220703,351.7485352 324.125,351.1235352 325.0019531,350.3706055 + 325.7519531,349.6176758 327.0058594,347.6157227 327.2539063,346.4868164 327.5058594,345.4858398 327.6308594,344.2348633 "/> + </g> +</g> +<path fill-rule="evenodd" clip-rule="evenodd" fill="#003882" d="M188.4228516,211.0395508V89.7011719h-23.2304688V66.4711914 + c7.4140625-0.3291016,13.8393555-2.3886719,19.2763672-6.1782227c5.4365234-3.7890625,9.3085938-8.7314453,11.6152344-14.8276367 + h23.7236328v165.5742188H188.4228516z"/> +</svg> diff --git a/tests/phpunit/data/media/iptc-invalid-psir.jpg b/tests/phpunit/data/media/iptc-invalid-psir.jpg Binary files differnew file mode 100644 index 00000000..01b9acf3 --- /dev/null +++ b/tests/phpunit/data/media/iptc-invalid-psir.jpg diff --git a/tests/phpunit/includes/ArticleTablesTest.php b/tests/phpunit/includes/ArticleTablesTest.php index 01776c95..02571b55 100644 --- a/tests/phpunit/includes/ArticleTablesTest.php +++ b/tests/phpunit/includes/ArticleTablesTest.php @@ -6,29 +6,28 @@ class ArticleTablesTest extends MediaWikiLangTestCase { function testbug14404() { - global $wgUser, $wgContLang, $wgLanguageCode, $wgLang; - - $title = Title::newFromText("Bug 14404"); - $article = new Article( $title ); - $wgUser = new User(); - $wgUser->mRights = array( 'createpage', 'edit', 'purge' ); + global $wgContLang, $wgLanguageCode, $wgLang; + + $title = Title::newFromText( 'Bug 14404' ); + $page = WikiPage::factory( $title ); + $user = new User(); + $user->mRights = array( 'createpage', 'edit', 'purge' ); $wgLanguageCode = 'es'; $wgContLang = Language::factory( 'es' ); - + $wgLang = Language::factory( 'fr' ); - $status = $article->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0 ); - $templates1 = $article->getUsedTemplates(); + $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0, false, $user ); + $templates1 = $page->getUsedTemplates(); $wgLang = Language::factory( 'de' ); - $article->mParserOptions = null; // Let it pick the new user language - $article->mPreparedEdit = false; // In order to force the rerendering of the same wikitext - + $page->mPreparedEdit = false; // In order to force the rerendering of the same wikitext + // We need an edit, a purge is not enough to regenerate the tables - $status = $article->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE ); - $templates2 = $article->getUsedTemplates(); - + $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE, false, $user ); + $templates2 = $page->getUsedTemplates(); + $this->assertEquals( $templates1, $templates2 ); $this->assertEquals( $templates1[0]->getFullText(), 'Historial' ); } - + } diff --git a/tests/phpunit/includes/ArticleTest.php b/tests/phpunit/includes/ArticleTest.php index 285efee9..846d2b86 100644 --- a/tests/phpunit/includes/ArticleTest.php +++ b/tests/phpunit/includes/ArticleTest.php @@ -19,25 +19,25 @@ class ArticleTest extends MediaWikiTestCase { } - function testImplementsGetMagic() { - $this->assertEquals( -1, $this->article->mCounter, "Article __get magic" ); + function testImplementsGetMagic() { + $this->assertEquals( false, $this->article->mLatest, "Article __get magic" ); } /** * @depends testImplementsGetMagic */ function testImplementsSetMagic() { - - $this->article->mCounter = 2; - $this->assertEquals( 2, $this->article->mCounter, "Article __set magic" ); + $this->article->mLatest = 2; + $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" ); } /** * @depends testImplementsSetMagic */ function testImplementsCallMagic() { - $this->article->mCounter = 33; - $this->assertEquals( 33, $this->article->getCount(), "Article __call magic" ); + $this->article->mLatest = 33; + $this->article->mDataLoaded = true; + $this->assertEquals( 33, $this->article->getLatest(), "Article __call magic" ); } function testGetOrSetOnNewProperty() { diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php index 2f224ba8..749f40b4 100644 --- a/tests/phpunit/includes/BlockTest.php +++ b/tests/phpunit/includes/BlockTest.php @@ -2,11 +2,10 @@ /** * @group Database + * @group Blocking */ class BlockTest extends MediaWikiLangTestCase { - const REASON = "Some reason"; - private $block, $madeAt; /* variable used to save up the blockID we insert in this test suite */ @@ -36,8 +35,8 @@ class BlockTest extends MediaWikiLangTestCase { $oldBlock->delete(); } - $this->block = new Block( 'UTBlockee', 1, 0, - self::REASON + $this->block = new Block( 'UTBlockee', $user->getID(), 0, + 'Parce que', 0, false, time() + 100500 ); $this->madeAt = wfTimestamp( TS_MW ); @@ -68,7 +67,7 @@ class BlockTest extends MediaWikiLangTestCase { // $this->dumpBlocks(); $this->assertTrue( $this->block->equals( Block::newFromTarget('UTBlockee') ), "newFromTarget() returns the same block as the one that was made"); - + $this->assertTrue( $this->block->equals( Block::newFromID( $this->blockId ) ), "newFromID() returns the same block as the one that was made"); } @@ -77,8 +76,9 @@ class BlockTest extends MediaWikiLangTestCase { * per bug 26425 */ function testBug26425BlockTimestampDefaultsToTime() { - - $this->assertEquals( $this->madeAt, $this->block->mTimestamp, "If no timestamp is specified, the block is recorded as time()"); + // delta to stop one-off errors when things happen to go over a second mark. + $delta = abs( $this->madeAt - $this->block->mTimestamp ); + $this->assertLessThan( 2, $delta, "If no timestamp is specified, the block is recorded as time()"); } @@ -91,6 +91,8 @@ class BlockTest extends MediaWikiLangTestCase { * @dataProvider dataBug29116 */ function testBug29116LoadWithEmptyIp( $vagueTarget ) { + $this->hideDeprecated( 'Block::load' ); + $uid = User::idFromName( 'UTBlockee' ); $this->assertTrue( ($uid > 0), 'Must be able to look up the target user during tests' ); @@ -121,4 +123,3 @@ class BlockTest extends MediaWikiLangTestCase { ); } } - diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php new file mode 100644 index 00000000..e98e9707 --- /dev/null +++ b/tests/phpunit/includes/EditPageTest.php @@ -0,0 +1,33 @@ +<?php + +class EditPageTest extends MediaWikiTestCase { + + /** + * @dataProvider dataExtractSectionTitle + */ + function testExtractSectionTitle( $section, $title ) { + $extracted = EditPage::extractSectionTitle( $section ); + $this->assertEquals( $title, $extracted ); + } + + function dataExtractSectionTitle() { + return array( + array( + "== Test ==\n\nJust a test section.", + "Test" + ), + array( + "An initial section, no header.", + false + ), + array( + "An initial section with a fake heder (bug 32617)\n\n== Test == ??\nwtf", + false + ), + array( + "== Section ==\nfollowed by a fake == Non-section == ??\nnoooo", + "Section" + ) + ); + } +} diff --git a/tests/phpunit/includes/ExtraParserTest.php b/tests/phpunit/includes/ExtraParserTest.php index 5b0aa98b..a9088cb2 100644 --- a/tests/phpunit/includes/ExtraParserTest.php +++ b/tests/phpunit/includes/ExtraParserTest.php @@ -10,11 +10,13 @@ class ExtraParserTest extends MediaWikiTestCase { global $wgContLang; global $wgShowDBErrorBacktrace; global $wgLanguageCode; + global $wgAlwaysUseTidy; $wgShowDBErrorBacktrace = true; $wgLanguageCode = 'en'; $wgContLang = new Language( 'en' ); $wgMemc = new EmptyBagOStuff; + $wgAlwaysUseTidy = false; $this->options = new ParserOptions; $this->options->setTemplateCallback( array( __CLASS__, 'statelessFetchTemplate' ) ); @@ -61,20 +63,48 @@ class ExtraParserTest extends MediaWikiTestCase { * cleanSig() makes all templates substs and removes tildes */ function testCleanSig() { + global $wgCleanSignatures; + $oldCleanSignature = $wgCleanSignatures; + $wgCleanSignatures = true; + $title = Title::newFromText( __FUNCTION__ ); $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" ); + + $wgCleanSignatures = $oldCleanSignature; $this->assertEquals( "{{SUBST:Foo}} ", $outputText ); } - + /** - * cleanSigInSig() just removes tildes + * cleanSig() should do nothing if disabled */ - function testCleanSigInSig() { + function testCleanSigDisabled() { + global $wgCleanSignatures; + $oldCleanSignature = $wgCleanSignatures; + $wgCleanSignatures = false; + $title = Title::newFromText( __FUNCTION__ ); - $outputText = $this->parser->cleanSigInSig( "{{Foo}} ~~~~" ); + $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" ); + + $wgCleanSignatures = $oldCleanSignature; - $this->assertEquals( "{{Foo}} ", $outputText ); + $this->assertEquals( "{{Foo}} ~~~~", $outputText ); + } + + /** + * cleanSigInSig() just removes tildes + * @dataProvider provideStringsForCleanSigInSig + */ + function testCleanSigInSig( $in, $out ) { + $this->assertEquals( Parser::cleanSigInSig( $in), $out ); + } + + function provideStringsForCleanSigInSig() { + return array( + array( "{{Foo}} ~~~~", "{{Foo}} " ), + array( "~~~", "" ), + array( "~~~~~", "" ), + ); } function testGetSection() { @@ -110,4 +140,28 @@ class ExtraParserTest extends MediaWikiTestCase { 'finalTitle' => $title, 'deps' => $deps ); } + + /** + * @group Database + */ + function testTrackingCategory() { + $title = Title::newFromText( __FUNCTION__ ); + $catName = wfMsgForContent( 'broken-file-category' ); + $cat = Title::makeTitleSafe( NS_CATEGORY, $catName ); + $expected = array( $cat->getDBkey() ); + $parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options ); + $result = $parserOutput->getCategoryLinks(); + $this->assertEquals( $expected, $result ); + } + + /** + * @group Database + */ + function testTrackingCategorySpecial() { + // Special pages shouldn't have tracking cats. + $title = SpecialPage::getTitleFor( 'Contributions' ); + $parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options ); + $result = $parserOutput->getCategoryLinks(); + $this->assertEmpty( $result ); + } } diff --git a/tests/phpunit/includes/FormOptionsInitializationTest.php b/tests/phpunit/includes/FormOptionsInitializationTest.php index 85d76271..d86c95d7 100644 --- a/tests/phpunit/includes/FormOptionsInitializationTest.php +++ b/tests/phpunit/includes/FormOptionsInitializationTest.php @@ -25,9 +25,9 @@ class FormOptionsExposed extends FormOptions { * * Generated by PHPUnit on 2011-02-28 at 20:46:27. * - * Copyright © 2011, Ashar Voultoiz + * Copyright © 2011, Antoine Musso * - * @author Ashar Voultoiz + * @author Antoine Musso */ class FormOptionsInitializationTest extends MediaWikiTestCase { /** diff --git a/tests/phpunit/includes/FormOptionsTest.php b/tests/phpunit/includes/FormOptionsTest.php index 86618d93..749343ec 100644 --- a/tests/phpunit/includes/FormOptionsTest.php +++ b/tests/phpunit/includes/FormOptionsTest.php @@ -12,9 +12,9 @@ * Test class for FormOptions methods. * Generated by PHPUnit on 2011-02-28 at 20:46:27. * - * Copyright © 2011, Ashar Voultoiz + * Copyright © 2011, Antoine Musso * - * @author Ashar Voultoiz + * @author Antoine Musso */ class FormOptionsTest extends MediaWikiTestCase { /** diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php index 3d157d0a..3cb42f12 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php @@ -94,24 +94,80 @@ class GlobalTest extends MediaWikiTestCase { $this->assertTrue( $end > $start, "Time is running backwards!" ); } - function testArrayToCGI() { + function dataArrayToCGI() { + return array( + array( array(), '' ), // empty + array( array( 'foo' => 'bar' ), 'foo=bar' ), // string test + array( array( 'foo' => '' ), 'foo=' ), // empty string test + array( array( 'foo' => 1 ), 'foo=1' ), // number test + array( array( 'foo' => true ), 'foo=1' ), // true test + array( array( 'foo' => false ), '' ), // false test + array( array( 'foo' => null ), '' ), // null test + array( array( 'foo' => 'A&B=5+6@!"\'' ), 'foo=A%26B%3D5%2B6%40%21%22%27' ), // urlencoding test + array( array( 'foo' => 'bar', 'baz' => 'is', 'asdf' => 'qwerty' ), 'foo=bar&baz=is&asdf=qwerty' ), // multi-item test + array( array( 'foo' => array( 'bar' => 'baz' ) ), 'foo%5Bbar%5D=baz' ), + array( array( 'foo' => array( 'bar' => 'baz', 'qwerty' => 'asdf' ) ), 'foo%5Bbar%5D=baz&foo%5Bqwerty%5D=asdf' ), + array( array( 'foo' => array( 'bar', 'baz' ) ), 'foo%5B0%5D=bar&foo%5B1%5D=baz' ), + array( array( 'foo' => array( 'bar' => array( 'bar' => 'baz' ) ) ), 'foo%5Bbar%5D%5Bbar%5D=baz' ), + ); + } + + /** + * @dataProvider dataArrayToCGI + */ + function testArrayToCGI( $array, $result ) { + $this->assertEquals( $result, wfArrayToCGI( $array ) ); + } + + + function testArrayToCGI2() { $this->assertEquals( - "baz=AT%26T&foo=bar", + "baz=bar&foo=bar", wfArrayToCGI( - array( 'baz' => 'AT&T', 'ignore' => '' ), + array( 'baz' => 'bar' ), array( 'foo' => 'bar', 'baz' => 'overridden value' ) ) ); - $this->assertEquals( - "path%5B0%5D=wiki&path%5B1%5D=test&cfg%5Bservers%5D%5Bhttp%5D=localhost", - wfArrayToCGI( array( - 'path' => array( 'wiki', 'test' ), - 'cfg' => array( 'servers' => array( 'http' => 'localhost' ) ) ) ) ); } - function testCgiToArray() { - $this->assertEquals( - array( 'path' => array( 'wiki', 'test' ), - 'cfg' => array( 'servers' => array( 'http' => 'localhost' ) ) ), - wfCgiToArray( 'path%5B0%5D=wiki&path%5B1%5D=test&cfg%5Bservers%5D%5Bhttp%5D=localhost' ) ); + function dataCgiToArray() { + return array( + array( '', array() ), // empty + array( 'foo=bar', array( 'foo' => 'bar' ) ), // string + array( 'foo=', array( 'foo' => '' ) ), // empty string + array( 'foo', array( 'foo' => '' ) ), // missing = + array( 'foo=bar&qwerty=asdf', array( 'foo' => 'bar', 'qwerty' => 'asdf' ) ), // multiple value + array( 'foo=A%26B%3D5%2B6%40%21%22%27', array( 'foo' => 'A&B=5+6@!"\'' ) ), // urldecoding test + array( 'foo%5Bbar%5D=baz', array( 'foo' => array( 'bar' => 'baz' ) ) ), + array( 'foo%5Bbar%5D=baz&foo%5Bqwerty%5D=asdf', array( 'foo' => array( 'bar' => 'baz', 'qwerty' => 'asdf' ) ) ), + array( 'foo%5B0%5D=bar&foo%5B1%5D=baz', array( 'foo' => array( 0 => 'bar', 1 => 'baz' ) ) ), + array( 'foo%5Bbar%5D%5Bbar%5D=baz', array( 'foo' => array( 'bar' => array( 'bar' => 'baz' ) ) ) ), + ); + } + + /** + * @dataProvider dataCgiToArray + */ + function testCgiToArray( $cgi, $result ) { + $this->assertEquals( $result, wfCgiToArray( $cgi ) ); + } + + function dataCgiRoundTrip() { + return array( + array( '' ), + array( 'foo=bar' ), + array( 'foo=' ), + array( 'foo=bar&baz=biz' ), + array( 'foo=A%26B%3D5%2B6%40%21%22%27' ), + array( 'foo%5Bbar%5D=baz' ), + array( 'foo%5B0%5D=bar&foo%5B1%5D=baz' ), + array( 'foo%5Bbar%5D%5Bbar%5D=baz' ), + ); + } + + /** + * @dataProvider dataCgiRoundTrip + */ + function testCgiRoundTrip( $cgi ) { + $this->assertEquals( $cgi, wfArrayToCGI( wfCgiToArray( $cgi ) ) ); } function testMimeTypeMatch() { @@ -176,263 +232,6 @@ class GlobalTest extends MediaWikiTestCase { array( 'text/*' => 1.0 ), array( 'application/xhtml+xml' => 1.0 ) ) ); } - - function testTimestamp() { - $t = gmmktime( 12, 34, 56, 1, 15, 2001 ); - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, $t ), - 'TS_UNIX to TS_MW' ); - $this->assertEquals( - '19690115123456', - wfTimestamp( TS_MW, -30281104 ), - 'Negative TS_UNIX to TS_MW' ); - $this->assertEquals( - 979562096, - wfTimestamp( TS_UNIX, $t ), - 'TS_UNIX to TS_UNIX' ); - $this->assertEquals( - '2001-01-15 12:34:56', - wfTimestamp( TS_DB, $t ), - 'TS_UNIX to TS_DB' ); - $this->assertEquals( - '20010115T123456Z', - wfTimestamp( TS_ISO_8601_BASIC, $t ), - 'TS_ISO_8601_BASIC to TS_DB' ); - - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, '20010115123456' ), - 'TS_MW to TS_MW' ); - $this->assertEquals( - 979562096, - wfTimestamp( TS_UNIX, '20010115123456' ), - 'TS_MW to TS_UNIX' ); - $this->assertEquals( - '2001-01-15 12:34:56', - wfTimestamp( TS_DB, '20010115123456' ), - 'TS_MW to TS_DB' ); - $this->assertEquals( - '20010115T123456Z', - wfTimestamp( TS_ISO_8601_BASIC, '20010115123456' ), - 'TS_MW to TS_ISO_8601_BASIC' ); - - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, '2001-01-15 12:34:56' ), - 'TS_DB to TS_MW' ); - $this->assertEquals( - 979562096, - wfTimestamp( TS_UNIX, '2001-01-15 12:34:56' ), - 'TS_DB to TS_UNIX' ); - $this->assertEquals( - '2001-01-15 12:34:56', - wfTimestamp( TS_DB, '2001-01-15 12:34:56' ), - 'TS_DB to TS_DB' ); - $this->assertEquals( - '20010115T123456Z', - wfTimestamp( TS_ISO_8601_BASIC, '2001-01-15 12:34:56' ), - 'TS_DB to TS_ISO_8601_BASIC' ); - - # rfc2822 section 3.3 - - $this->assertEquals( - 'Mon, 15 Jan 2001 12:34:56 GMT', - wfTimestamp( TS_RFC2822, '20010115123456' ), - 'TS_MW to TS_RFC2822' ); - - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, 'Mon, 15 Jan 2001 12:34:56 GMT' ), - 'TS_RFC2822 to TS_MW' ); - - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, ' Mon, 15 Jan 2001 12:34:56 GMT' ), - 'TS_RFC2822 with leading space to TS_MW' ); - - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, '15 Jan 2001 12:34:56 GMT' ), - 'TS_RFC2822 without optional day-of-week to TS_MW' ); - - # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space - # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2 - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, 'Mon, 15 Jan 2001 12:34:56 GMT' ), - 'TS_RFC2822 to TS_MW' ); - - # WSP = SP / HTAB ; rfc2234 - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, "Mon, 15 Jan\x092001 12:34:56 GMT" ), - 'TS_RFC2822 with HTAB to TS_MW' ); - - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, "Mon, 15 Jan\x09 \x09 2001 12:34:56 GMT" ), - 'TS_RFC2822 with HTAB and SP to TS_MW' ); - - $this->assertEquals( - '19941106084937', - wfTimestamp( TS_MW, "Sun, 6 Nov 94 08:49:37 GMT" ), - 'TS_RFC2822 with obsolete year to TS_MW' ); - } - - /** - * This test checks wfTimestamp() with values outside. - * It needs PHP 64 bits or PHP > 5.1. - * See r74778 and bug 25451 - */ - function testOldTimestamps() { - $this->assertEquals( 'Fri, 13 Dec 1901 20:45:54 GMT', - wfTimestamp( TS_RFC2822, '19011213204554' ), - 'Earliest time according to php documentation' ); - - $this->assertEquals( 'Tue, 19 Jan 2038 03:14:07 GMT', - wfTimestamp( TS_RFC2822, '20380119031407' ), - 'Latest 32 bit time' ); - - $this->assertEquals( '-2147483648', - wfTimestamp( TS_UNIX, '19011213204552' ), - 'Earliest 32 bit unix time' ); - - $this->assertEquals( '2147483647', - wfTimestamp( TS_UNIX, '20380119031407' ), - 'Latest 32 bit unix time' ); - - $this->assertEquals( 'Fri, 13 Dec 1901 20:45:52 GMT', - wfTimestamp( TS_RFC2822, '19011213204552' ), - 'Earliest 32 bit time' ); - - $this->assertEquals( 'Fri, 13 Dec 1901 20:45:51 GMT', - wfTimestamp( TS_RFC2822, '19011213204551' ), - 'Earliest 32 bit time - 1' ); - - $this->assertEquals( 'Tue, 19 Jan 2038 03:14:08 GMT', - wfTimestamp( TS_RFC2822, '20380119031408' ), - 'Latest 32 bit time + 1' ); - - $this->assertEquals( '19011212000000', - wfTimestamp(TS_MW, '19011212000000'), - 'Convert to itself r74778#c10645' ); - - $this->assertEquals( '-2147483649', - wfTimestamp( TS_UNIX, '19011213204551' ), - 'Earliest 32 bit unix time - 1' ); - - $this->assertEquals( '2147483648', - wfTimestamp( TS_UNIX, '20380119031408' ), - 'Latest 32 bit unix time + 1' ); - - $this->assertEquals( '19011213204551', - wfTimestamp( TS_MW, '-2147483649' ), - '1901 negative unix time to MediaWiki' ); - - $this->assertEquals( '18010115123456', - wfTimestamp( TS_MW, '-5331871504' ), - '1801 negative unix time to MediaWiki' ); - - $this->assertEquals( 'Tue, 09 Aug 0117 12:34:56 GMT', - wfTimestamp( TS_RFC2822, '0117-08-09 12:34:56'), - 'Death of Roman Emperor [[Trajan]]'); - - /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */ - - $this->assertEquals( 'Sun, 01 Jan 0101 00:00:00 GMT', - wfTimestamp( TS_RFC2822, '-58979923200'), - '1/1/101'); - - $this->assertEquals( 'Mon, 01 Jan 0001 00:00:00 GMT', - wfTimestamp( TS_RFC2822, '-62135596800'), - 'Year 1'); - - /* It is not clear if we should generate a year 0 or not - * We are completely off RFC2822 requirement of year being - * 1900 or later. - */ - $this->assertEquals( 'Wed, 18 Oct 0000 00:00:00 GMT', - wfTimestamp( TS_RFC2822, '-62142076800'), - 'ISO 8601:2004 [[year 0]], also called [[1 BC]]'); - } - - function testHttpDate() { - # The Resource Loader uses wfTimestamp() to convert timestamps - # from If-Modified-Since header. - # Thus it must be able to parse all rfc2616 date formats - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1 - - $this->assertEquals( - '19941106084937', - wfTimestamp( TS_MW, 'Sun, 06 Nov 1994 08:49:37 GMT' ), - 'RFC 822 date' ); - - $this->assertEquals( - '19941106084937', - wfTimestamp( TS_MW, 'Sunday, 06-Nov-94 08:49:37 GMT' ), - 'RFC 850 date' ); - - $this->assertEquals( - '19941106084937', - wfTimestamp( TS_MW, 'Sun Nov 6 08:49:37 1994' ), - "ANSI C's asctime() format" ); - - // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171 - $this->assertEquals( - '20101122141242', - wfTimestamp( TS_MW, 'Mon, 22 Nov 2010 14:12:42 GMT; length=52626' ), - "Netscape extension to HTTP/1.0" ); - - } - - function testTimestampParameter() { - // There are a number of assumptions in our codebase where wfTimestamp() should give - // the current date but it is not given a 0 there. See r71751 CR - - $now = wfTimestamp( TS_UNIX ); - // We check that wfTimestamp doesn't return false (error) and use a LessThan assert - // for the cases where the test is run in a second boundary. - - $zero = wfTimestamp( TS_UNIX, 0 ); - $this->assertNotEquals( false, $zero ); - $this->assertLessThan( 5, $zero - $now ); - - $empty = wfTimestamp( TS_UNIX, '' ); - $this->assertNotEquals( false, $empty ); - $this->assertLessThan( 5, $empty - $now ); - - $null = wfTimestamp( TS_UNIX, null ); - $this->assertNotEquals( false, $null ); - $this->assertLessThan( 5, $null - $now ); - } - - function testBasename() { - $sets = array( - '' => '', - '/' => '', - '\\' => '', - '//' => '', - '\\\\' => '', - 'a' => 'a', - 'aaaa' => 'aaaa', - '/a' => 'a', - '\\a' => 'a', - '/aaaa' => 'aaaa', - '\\aaaa' => 'aaaa', - '/aaaa/' => 'aaaa', - '\\aaaa\\' => 'aaaa', - '\\aaaa\\' => 'aaaa', - '/mnt/upload3/wikipedia/en/thumb/8/8b/Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg' => '93px-Zork_Grand_Inquisitor_box_cover.jpg', - 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE' => 'VIEWER.EXE', - 'Östergötland_coat_of_arms.png' => 'Östergötland_coat_of_arms.png', - ); - foreach ( $sets as $from => $to ) { - $this->assertEquals( $to, wfBaseName( $from ), - "wfBaseName('$from') => '$to'" ); - } - } - function testFallbackMbstringFunctions() { @@ -701,135 +500,6 @@ class GlobalTest extends MediaWikiTestCase { ); } - - /** - * test @see wfBCP47(). - * Please note the BCP explicitly state that language codes are case - * insensitive, there are some exceptions to the rule :) - * This test is used to verify our formatting against all lower and - * all upper cases language code. - * - * @see http://tools.ietf.org/html/bcp47 - * @dataProvider provideLanguageCodes() - */ - function testBCP47( $code, $expected ) { - $code = strtolower( $code ); - $this->assertEquals( $expected, wfBCP47($code), - "Applying BCP47 standard to lower case '$code'" - ); - - $code = strtoupper( $code ); - $this->assertEquals( $expected, wfBCP47($code), - "Applying BCP47 standard to upper case '$code'" - ); - } - - /** - * Array format is ($code, $expected) - */ - function provideLanguageCodes() { - return array( - // Extracted from BCP47 (list not exhaustive) - # 2.1.1 - array( 'en-ca-x-ca' , 'en-CA-x-ca' ), - array( 'sgn-be-fr' , 'sgn-BE-FR' ), - array( 'az-latn-x-latn', 'az-Latn-x-latn' ), - # 2.2 - array( 'sr-Latn-RS', 'sr-Latn-RS' ), - array( 'az-arab-ir', 'az-Arab-IR' ), - - # 2.2.5 - array( 'sl-nedis' , 'sl-nedis' ), - array( 'de-ch-1996', 'de-CH-1996' ), - - # 2.2.6 - array( - 'en-latn-gb-boont-r-extended-sequence-x-private', - 'en-Latn-GB-boont-r-extended-sequence-x-private' - ), - - // Examples from BCP47 Appendix A - # Simple language subtag: - array( 'DE', 'de' ), - array( 'fR', 'fr' ), - array( 'ja', 'ja' ), - - # Language subtag plus script subtag: - array( 'zh-hans', 'zh-Hans'), - array( 'sr-cyrl', 'sr-Cyrl'), - array( 'sr-latn', 'sr-Latn'), - - # Extended language subtags and their primary language subtag - # counterparts: - array( 'zh-cmn-hans-cn', 'zh-cmn-Hans-CN' ), - array( 'cmn-hans-cn' , 'cmn-Hans-CN' ), - array( 'zh-yue-hk' , 'zh-yue-HK' ), - array( 'yue-hk' , 'yue-HK' ), - - # Language-Script-Region: - array( 'zh-hans-cn', 'zh-Hans-CN' ), - array( 'sr-latn-RS', 'sr-Latn-RS' ), - - # Language-Variant: - array( 'sl-rozaj' , 'sl-rozaj' ), - array( 'sl-rozaj-biske', 'sl-rozaj-biske' ), - array( 'sl-nedis' , 'sl-nedis' ), - - # Language-Region-Variant: - array( 'de-ch-1901' , 'de-CH-1901' ), - array( 'sl-it-nedis' , 'sl-IT-nedis' ), - - # Language-Script-Region-Variant: - array( 'hy-latn-it-arevela', 'hy-Latn-IT-arevela' ), - - # Language-Region: - array( 'de-de' , 'de-DE' ), - array( 'en-us' , 'en-US' ), - array( 'es-419', 'es-419'), - - # Private use subtags: - array( 'de-ch-x-phonebk' , 'de-CH-x-phonebk' ), - array( 'az-arab-x-aze-derbend', 'az-Arab-x-aze-derbend' ), - /** - * Previous test does not reflect the BCP which states: - * az-Arab-x-AZE-derbend - * AZE being private, it should be lower case, hence the test above - * should probably be: - #array( 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ), - */ - - # Private use registry values: - array( 'x-whatever', 'x-whatever' ), - array( 'qaa-qaaa-qm-x-southern', 'qaa-Qaaa-QM-x-southern' ), - array( 'de-qaaa' , 'de-Qaaa' ), - array( 'sr-latn-qm', 'sr-Latn-QM' ), - array( 'sr-qaaa-rs', 'sr-Qaaa-RS' ), - - # Tags that use extensions - array( 'en-us-u-islamcal', 'en-US-u-islamcal' ), - array( 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ), - array( 'en-a-myext-b-another', 'en-a-myext-b-another' ), - - # Invalid: - // de-419-DE - // a-DE - // ar-a-aaa-b-bbb-a-ccc - - /* - // ISO 15924 : - array( 'sr-Cyrl', 'sr-Cyrl' ), - # @todo FIXME: Fix our function? - array( 'SR-lATN', 'sr-Latn' ), - array( 'fr-latn', 'fr-Latn' ), - // Use lowercase for single segment - // ISO 3166-1-alpha-2 code - array( 'US', 'us' ), # USA - array( 'uS', 'us' ), # USA - array( 'Fr', 'fr' ), # France - array( 'va', 'va' ), # Holy See (Vatican City State) - */); - } - /** * @dataProvider provideMakeUrlIndexes() */ @@ -886,7 +556,63 @@ class GlobalTest extends MediaWikiTestCase { ), ); } + + /** + * @dataProvider provideWfMatchesDomainList + */ + function testWfMatchesDomainList( $url, $domains, $expected, $description ) { + $actual = wfMatchesDomainList( $url, $domains ); + $this->assertEquals( $expected, $actual, $description ); + } + + function provideWfMatchesDomainList() { + $a = array(); + $protocols = array( 'HTTP' => 'http:', 'HTTPS' => 'https:', 'protocol-relative' => '' ); + foreach ( $protocols as $pDesc => $p ) { + $a = array_merge( $a, array( + array( "$p//www.example.com", array(), false, "No matches for empty domains array, $pDesc URL" ), + array( "$p//www.example.com", array( 'www.example.com' ), true, "Exact match in domains array, $pDesc URL" ), + array( "$p//www.example.com", array( 'example.com' ), true, "Match without subdomain in domains array, $pDesc URL" ), + array( "$p//www.example2.com", array( 'www.example.com', 'www.example2.com', 'www.example3.com' ), true, "Exact match with other domains in array, $pDesc URL" ), + array( "$p//www.example2.com", array( 'example.com', 'example2.com', 'example3,com' ), true, "Match without subdomain with other domains in array, $pDesc URL" ), + array( "$p//www.example4.com", array( 'example.com', 'example2.com', 'example3,com' ), false, "Domain not in array, $pDesc URL" ), + + // FIXME: This is a bug in wfMatchesDomainList(). If and when this is fixed, update this test case + array( "$p//nds-nl.wikipedia.org", array( 'nl.wikipedia.org' ), true, "Substrings of domains match while they shouldn't, $pDesc URL" ), + ) ); + } + return $a; + } + /** + * @dataProvider provideWfShellMaintenanceCmdList + */ + function testWfShellMaintenanceCmd( $script, $parameters, $options, $expected, $description ) { + if( wfIsWindows() ) { + // Approximation that's good enough for our purposes just now + $expected = str_replace( "'", '"', $expected ); + } + $actual = wfShellMaintenanceCmd( $script, $parameters, $options ); + $this->assertEquals( $expected, $actual, $description ); + } + + function provideWfShellMaintenanceCmdList() { + global $wgPhpCli; + return array( + array( 'eval.php', array( '--help', '--test' ), array(), + "'$wgPhpCli' 'eval.php' '--help' '--test'", + "Called eval.php --help --test" ), + array( 'eval.php', array( '--help', '--test space' ), array('php' => 'php5'), + "'php5' 'eval.php' '--help' '--test space'", + "Called eval.php --help --test with php option" ), + array( 'eval.php', array( '--help', '--test', 'X' ), array('wrapper' => 'MWScript.php'), + "'$wgPhpCli' 'MWScript.php' 'eval.php' '--help' '--test' 'X'", + "Called eval.php --help --test with wrapper option" ), + array( 'eval.php', array( '--help', '--test', 'y' ), array('php' => 'php5', 'wrapper' => 'MWScript.php'), + "'php5' 'MWScript.php' 'eval.php' '--help' '--test' 'y'", + "Called eval.php --help --test with wrapper and php option" ), + ); + } /* TODO: many more! */ } diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php new file mode 100644 index 00000000..4879a38d --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php @@ -0,0 +1,29 @@ +<?php + +/** + * @group Database + */ +class GlobalWithDBTest extends MediaWikiTestCase { + /** + * @dataProvider provideWfIsBadImageList + */ + function testWfIsBadImage( $name, $title, $blacklist, $expected, $desc ) { + $this->assertEquals( $expected, wfIsBadImage( $name, $title, $blacklist ), $desc ); + } + + function provideWfIsBadImageList() { + $blacklist = '* [[File:Bad.jpg]] except [[Nasty page]]'; + return array( + array( 'Bad.jpg', false, $blacklist, true, + 'Called on a bad image' ), + array( 'Bad.jpg', Title::makeTitle( NS_MAIN, 'A page' ), $blacklist, true, + 'Called on a bad image' ), + array( 'NotBad.jpg', false, $blacklist, false, + 'Called on a non-bad image' ), + array( 'Bad.jpg', Title::makeTitle( NS_MAIN, 'Nasty page' ), $blacklist, false, + 'Called on a bad image but is on a whitelisted page' ), + array( 'File:Bad.jpg', false, $blacklist, false, + 'Called on a bad image with File:' ), + ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php new file mode 100644 index 00000000..be6c99e7 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php @@ -0,0 +1,111 @@ +<?php +/** + * Unit tests for wfAssembleUrl() + */ + +class wfAssembleUrl extends MediaWikiTestCase { + /** @dataProvider provideURLParts */ + public function testWfAssembleUrl( $parts, $output ) { + $partsDump = print_r( $parts, true ); + $this->assertEquals( + $output, + wfAssembleUrl( $parts ), + "Testing $partsDump assembles to $output" + ); + } + + /** + * Provider of URL parts for testing wfAssembleUrl() + * + * @return array + */ + public function provideURLParts() { + $schemes = array( + '' => array(), + '//' => array( + 'delimiter' => '//', + ), + 'http://' => array( + 'scheme' => 'http', + 'delimiter' => '://', + ), + ); + + $hosts = array( + '' => array(), + 'example.com' => array( + 'host' => 'example.com', + ), + 'example.com:123' => array( + 'host' => 'example.com', + 'port' => 123, + ), + 'id@example.com' => array( + 'user' => 'id', + 'host' => 'example.com', + ), + 'id@example.com:123' => array( + 'user' => 'id', + 'host' => 'example.com', + 'port' => 123, + ), + 'id:key@example.com' => array( + 'user' => 'id', + 'pass' => 'key', + 'host' => 'example.com', + ), + 'id:key@example.com:123' => array( + 'user' => 'id', + 'pass' => 'key', + 'host' => 'example.com', + 'port' => 123, + ), + ); + + $cases = array(); + foreach ( $schemes as $scheme => $schemeParts ) { + foreach ( $hosts as $host => $hostParts ) { + foreach ( array( '', '/path' ) as $path ) { + foreach ( array( '', 'query' ) as $query ) { + foreach ( array( '', 'fragment' ) as $fragment ) { + $parts = array_merge( + $schemeParts, + $hostParts + ); + $url = $scheme . + $host . + $path; + + if ( $path ) { + $parts['path'] = $path; + } + if ( $query ) { + $parts['query'] = $query; + $url .= '?' . $query; + } + if( $fragment ) { + $parts['fragment'] = $fragment; + $url .= '#' . $fragment; + } + + + $cases[] = array( + $parts, + $url, + ); + } + } + } + } + } + + $complexURL = 'http://id:key@example.org:321' . + '/over/there?name=ferret&foo=bar#nose'; + $cases[] = array( + wfParseUrl( $complexURL ), + $complexURL, + ); + + return $cases; + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php new file mode 100644 index 00000000..f4ec7a5f --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php @@ -0,0 +1,133 @@ +<?php +/** + * Unit tests for wfBCP47() + */ +class wfBCP47 extends MediaWikiTestCase { + /** + * test @see wfBCP47(). + * Please note the BCP explicitly state that language codes are case + * insensitive, there are some exceptions to the rule :) + * This test is used to verify our formatting against all lower and + * all upper cases language code. + * + * @see http://tools.ietf.org/html/bcp47 + * @dataProvider provideLanguageCodes() + */ + function testBCP47( $code, $expected ) { + $code = strtolower( $code ); + $this->assertEquals( $expected, wfBCP47($code), + "Applying BCP47 standard to lower case '$code'" + ); + + $code = strtoupper( $code ); + $this->assertEquals( $expected, wfBCP47($code), + "Applying BCP47 standard to upper case '$code'" + ); + } + + /** + * Array format is ($code, $expected) + */ + function provideLanguageCodes() { + return array( + // Extracted from BCP47 (list not exhaustive) + # 2.1.1 + array( 'en-ca-x-ca' , 'en-CA-x-ca' ), + array( 'sgn-be-fr' , 'sgn-BE-FR' ), + array( 'az-latn-x-latn', 'az-Latn-x-latn' ), + # 2.2 + array( 'sr-Latn-RS', 'sr-Latn-RS' ), + array( 'az-arab-ir', 'az-Arab-IR' ), + + # 2.2.5 + array( 'sl-nedis' , 'sl-nedis' ), + array( 'de-ch-1996', 'de-CH-1996' ), + + # 2.2.6 + array( + 'en-latn-gb-boont-r-extended-sequence-x-private', + 'en-Latn-GB-boont-r-extended-sequence-x-private' + ), + + // Examples from BCP47 Appendix A + # Simple language subtag: + array( 'DE', 'de' ), + array( 'fR', 'fr' ), + array( 'ja', 'ja' ), + + # Language subtag plus script subtag: + array( 'zh-hans', 'zh-Hans'), + array( 'sr-cyrl', 'sr-Cyrl'), + array( 'sr-latn', 'sr-Latn'), + + # Extended language subtags and their primary language subtag + # counterparts: + array( 'zh-cmn-hans-cn', 'zh-cmn-Hans-CN' ), + array( 'cmn-hans-cn' , 'cmn-Hans-CN' ), + array( 'zh-yue-hk' , 'zh-yue-HK' ), + array( 'yue-hk' , 'yue-HK' ), + + # Language-Script-Region: + array( 'zh-hans-cn', 'zh-Hans-CN' ), + array( 'sr-latn-RS', 'sr-Latn-RS' ), + + # Language-Variant: + array( 'sl-rozaj' , 'sl-rozaj' ), + array( 'sl-rozaj-biske', 'sl-rozaj-biske' ), + array( 'sl-nedis' , 'sl-nedis' ), + + # Language-Region-Variant: + array( 'de-ch-1901' , 'de-CH-1901' ), + array( 'sl-it-nedis' , 'sl-IT-nedis' ), + + # Language-Script-Region-Variant: + array( 'hy-latn-it-arevela', 'hy-Latn-IT-arevela' ), + + # Language-Region: + array( 'de-de' , 'de-DE' ), + array( 'en-us' , 'en-US' ), + array( 'es-419', 'es-419'), + + # Private use subtags: + array( 'de-ch-x-phonebk' , 'de-CH-x-phonebk' ), + array( 'az-arab-x-aze-derbend', 'az-Arab-x-aze-derbend' ), + /** + * Previous test does not reflect the BCP which states: + * az-Arab-x-AZE-derbend + * AZE being private, it should be lower case, hence the test above + * should probably be: + #array( 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ), + */ + + # Private use registry values: + array( 'x-whatever', 'x-whatever' ), + array( 'qaa-qaaa-qm-x-southern', 'qaa-Qaaa-QM-x-southern' ), + array( 'de-qaaa' , 'de-Qaaa' ), + array( 'sr-latn-qm', 'sr-Latn-QM' ), + array( 'sr-qaaa-rs', 'sr-Qaaa-RS' ), + + # Tags that use extensions + array( 'en-us-u-islamcal', 'en-US-u-islamcal' ), + array( 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ), + array( 'en-a-myext-b-another', 'en-a-myext-b-another' ), + + # Invalid: + // de-419-DE + // a-DE + // ar-a-aaa-b-bbb-a-ccc + + /* + // ISO 15924 : + array( 'sr-Cyrl', 'sr-Cyrl' ), + # @todo FIXME: Fix our function? + array( 'SR-lATN', 'sr-Latn' ), + array( 'fr-latn', 'fr-Latn' ), + // Use lowercase for single segment + // ISO 3166-1-alpha-2 code + array( 'US', 'us' ), # USA + array( 'uS', 'us' ), # USA + array( 'Fr', 'fr' ), # France + array( 'va', 'va' ), # Holy See (Vatican City State) + */); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php new file mode 100644 index 00000000..59954b23 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php @@ -0,0 +1,36 @@ +<?php +/** + * Tests for wfBaseName() + */ +class wfBaseName extends MediaWikiTestCase { + /** + * @dataProvider providePaths + */ + function testBaseName( $fullpath, $basename ) { + $this->assertEquals( $basename, wfBaseName( $fullpath ), + "wfBaseName('$fullpath') => '$basename'" ); + } + + function providePaths() { + return array( + array( '', '' ), + array( '/', '' ), + array( '\\', '' ), + array( '//', '' ), + array( '\\\\', '' ), + array( 'a', 'a' ), + array( 'aaaa', 'aaaa' ), + array( '/a', 'a' ), + array( '\\a', 'a' ), + array( '/aaaa', 'aaaa' ), + array( '\\aaaa', 'aaaa' ), + array( '/aaaa/', 'aaaa' ), + array( '\\aaaa\\', 'aaaa' ), + array( '\\aaaa\\', 'aaaa' ), + array( '/mnt/upload3/wikipedia/en/thumb/8/8b/Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg', + '93px-Zork_Grand_Inquisitor_box_cover.jpg' ), + array( 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE', 'VIEWER.EXE' ), + array( 'Östergötland_coat_of_arms.png', 'Östergötland_coat_of_arms.png' ), + ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php index b388b266..192689f8 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php +++ b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php @@ -1,5 +1,5 @@ <?php -/* +/** * Unit tests for wfExpandUrl() */ @@ -12,16 +12,16 @@ class wfExpandUrl extends MediaWikiTestCase { $oldCanServer = $wgCanonicalServer; $wgServer = $server; $wgCanonicalServer = $canServer; - + // Fake $_SERVER['HTTPS'] if needed if ( $httpsMode ) { $_SERVER['HTTPS'] = 'on'; } else { unset( $_SERVER['HTTPS'] ); } - + $this->assertEquals( $fullUrl, wfExpandUrl( $shortUrl, $defaultProto ), $message ); - + // Restore $wgServer and $wgCanonicalServer $wgServer = $oldServer; $wgCanonicalServer = $oldCanServer; @@ -29,12 +29,14 @@ class wfExpandUrl extends MediaWikiTestCase { /** * Provider of URL examples for testing wfExpandUrl() + * + * @return array */ public function provideExpandableUrls() { $modes = array( 'http', 'https' ); $servers = array( 'http' => 'http://example.com', 'https' => 'https://example.com', 'protocol-relative' => '//example.com' ); $defaultProtos = array( 'http' => PROTO_HTTP, 'https' => PROTO_HTTPS, 'protocol-relative' => PROTO_RELATIVE, 'current' => PROTO_CURRENT, 'canonical' => PROTO_CANONICAL ); - + $retval = array(); foreach ( $modes as $mode ) { $httpsMode = $mode == 'https'; @@ -46,14 +48,14 @@ class wfExpandUrl extends MediaWikiTestCase { $retval[] = array( 'https://example.com', 'https://example.com', $defaultProto, $server, $canServer, $httpsMode, "Testing fully qualified https URLs (no need to expand) (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); # Would be nice to support this, see fixme on wfExpandUrl() $retval[] = array( "wiki/FooBar", 'wiki/FooBar', $defaultProto, $server, $canServer, $httpsMode, "Test non-expandable relative URLs (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); - + // Determine expected protocol $p = $protoDesc . ':'; // default case if ( $protoDesc == 'protocol-relative' ) { $p = ''; - } else if ( $protoDesc == 'current' ) { + } elseif ( $protoDesc == 'current' ) { $p = "$mode:"; - } else if ( $protoDesc == 'canonical' ) { + } elseif ( $protoDesc == 'canonical' ) { $p = "$canServerMode:"; } else { $p = $protoDesc . ':'; @@ -61,12 +63,12 @@ class wfExpandUrl extends MediaWikiTestCase { // Determine expected server name if ( $protoDesc == 'canonical' ) { $srv = $canServer; - } else if ( $serverDesc == 'protocol-relative' ) { + } elseif ( $serverDesc == 'protocol-relative' ) { $srv = $p . $server; } else { $srv = $server; } - + $retval[] = array( "$p//wikipedia.org", '//wikipedia.org', $defaultProto, $server, $canServer, $httpsMode, "Test protocol-relative URL (defaultProto: $protoDesc, wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); $retval[] = array( "$srv/wiki/FooBar", '/wiki/FooBar', $defaultProto, $server, $canServer, $httpsMode, "Testing expanding URL beginning with / (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); } diff --git a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php new file mode 100644 index 00000000..1cf0e0fb --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php @@ -0,0 +1,90 @@ +<?php +/** + * Unit tests for wfRemoveDotSegments() + */ + +class wfRemoveDotSegments extends MediaWikiTestCase { + /** @dataProvider providePaths */ + public function testWfRemoveDotSegments( $inputPath, $outputPath ) { + $this->assertEquals( + $outputPath, + wfRemoveDotSegments( $inputPath ), + "Testing $inputPath expands to $outputPath" + ); + } + + /** + * Provider of URL paths for testing wfRemoveDotSegments() + * + * @return array + */ + public function providePaths() { + return array( + array( '/a/b/c/./../../g', '/a/g' ), + array( 'mid/content=5/../6', 'mid/6' ), + array( '/a//../b', '/a/b' ), + array( '/.../a', '/.../a' ), + array( '.../a', '.../a' ), + array( '', '' ), + array( '/', '/' ), + array( '//', '//' ), + array( '.', '' ), + array( '..', '' ), + array( '...', '...' ), + array( '/.', '/' ), + array( '/..', '/' ), + array( './', '' ), + array( '../', '' ), + array( './a', 'a' ), + array( '../a', 'a' ), + array( '../../a', 'a' ), + array( '.././a', 'a' ), + array( './../a', 'a' ), + array( '././a', 'a' ), + array( '../../', '' ), + array( '.././', '' ), + array( './../', '' ), + array( '././', '' ), + array( '../..', '' ), + array( '../.', '' ), + array( './..', '' ), + array( './.', '' ), + array( '/../../a', '/a' ), + array( '/.././a', '/a' ), + array( '/./../a', '/a' ), + array( '/././a', '/a' ), + array( '/../../', '/' ), + array( '/.././', '/' ), + array( '/./../', '/' ), + array( '/././', '/' ), + array( '/../..', '/' ), + array( '/../.', '/' ), + array( '/./..', '/' ), + array( '/./.', '/' ), + array( 'b/../../a', '/a' ), + array( 'b/.././a', '/a' ), + array( 'b/./../a', '/a' ), + array( 'b/././a', 'b/a' ), + array( 'b/../../', '/' ), + array( 'b/.././', '/' ), + array( 'b/./../', '/' ), + array( 'b/././', 'b/' ), + array( 'b/../..', '/' ), + array( 'b/../.', '/' ), + array( 'b/./..', '/' ), + array( 'b/./.', 'b/' ), + array( '/b/../../a', '/a' ), + array( '/b/.././a', '/a' ), + array( '/b/./../a', '/a' ), + array( '/b/././a', '/b/a' ), + array( '/b/../../', '/' ), + array( '/b/.././', '/' ), + array( '/b/./../', '/' ), + array( '/b/././', '/b/' ), + array( '/b/../..', '/' ), + array( '/b/../.', '/' ), + array( '/b/./..', '/' ), + array( '/b/./.', '/b/' ), + ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php new file mode 100644 index 00000000..1df26d2c --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php @@ -0,0 +1,28 @@ +<?php + +class wfShorthandToIntegerTest extends MediaWikiTestCase { + /** + * @dataProvider provideABunchOfShorthands + */ + function testWfShorthandToInteger( $input, $output, $description ) { + $this->assertEquals( + wfShorthandToInteger( $input ), + $output, + $description + ); + } + + function provideABunchOfShorthands() { + return array( + array( '', -1, 'Empty string' ), + array( ' ', -1, 'String of spaces' ), + array( '1G', 1024 * 1024 * 1024, 'One gig uppercased' ), + array( '1g', 1024 * 1024 * 1024, 'One gig lowercased' ), + array( '1M', 1024 * 1024, 'One meg uppercased' ), + array( '1m', 1024 * 1024, 'One meg lowercased' ), + array( '1K', 1024, 'One kb uppercased' ), + array( '1k', 1024, 'One kb lowercased' ), + ); + } + +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php new file mode 100644 index 00000000..505c28c7 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php @@ -0,0 +1,134 @@ +<?php + +/* + * Tests for wfTimestamp() + */ +class wfTimestamp extends MediaWikiTestCase { + /** + * @dataProvider provideNormalTimestamps + */ + function testNormalTimestamps( $input, $format, $output, $desc ) { + $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc ); + } + + function provideNormalTimestamps() { + $t = gmmktime( 12, 34, 56, 1, 15, 2001 ); + return array ( + // TS_UNIX + array( $t, TS_MW, '20010115123456', 'TS_UNIX to TS_MW' ), + array( -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ), + array( $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ), + array( $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ), + + array( $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ), + + // TS_MW + array( '20010115123456', TS_MW, '20010115123456', 'TS_MW to TS_MW' ), + array( '20010115123456', TS_UNIX, 979562096, 'TS_MW to TS_UNIX' ), + array( '20010115123456', TS_DB, '2001-01-15 12:34:56', 'TS_MW to TS_DB' ), + array( '20010115123456', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_MW to TS_ISO_8601_BASIC' ), + + // TS_DB + array( '2001-01-15 12:34:56', TS_MW, '20010115123456', 'TS_DB to TS_MW' ), + array( '2001-01-15 12:34:56', TS_UNIX, 979562096, 'TS_DB to TS_UNIX' ), + array( '2001-01-15 12:34:56', TS_DB, '2001-01-15 12:34:56', 'TS_DB to TS_DB' ), + array( '2001-01-15 12:34:56', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_DB to TS_ISO_8601_BASIC' ), + + # rfc2822 section 3.3 + array( '20010115123456', TS_RFC2822, 'Mon, 15 Jan 2001 12:34:56 GMT', 'TS_MW to TS_RFC2822' ), + array( 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ), + array( ' Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 with leading space to TS_MW' ), + array( '15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 without optional day-of-week to TS_MW' ), + + # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space + # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2 + array( 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ), + + # WSP = SP / HTAB ; rfc2234 + array( "Mon, 15 Jan\x092001 12:34:56 GMT", TS_MW, '20010115123456', 'TS_RFC2822 with HTAB to TS_MW' ), + array( "Mon, 15 Jan\x09 \x09 2001 12:34:56 GMT", TS_MW, '20010115123456', 'TS_RFC2822 with HTAB and SP to TS_MW' ), + array( 'Sun, 6 Nov 94 08:49:37 GMT', TS_MW, '19941106084937', 'TS_RFC2822 with obsolete year to TS_MW' ), + ); + } + + /** + * This test checks wfTimestamp() with values outside. + * It needs PHP 64 bits or PHP > 5.1. + * See r74778 and bug 25451 + * @dataProvider provideOldTimestamps + */ + function testOldTimestamps( $input, $format, $output, $desc ) { + $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc ); + } + + function provideOldTimestamps() { + return array ( + array( '19011213204554', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:54 GMT', 'Earliest time according to php documentation' ), + array( '20380119031407', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:07 GMT', 'Latest 32 bit time' ), + array( '19011213204552', TS_UNIX, '-2147483648', 'Earliest 32 bit unix time' ), + array( '20380119031407', TS_UNIX, '2147483647', 'Latest 32 bit unix time' ), + array( '19011213204552', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:52 GMT', 'Earliest 32 bit time' ), + array( '19011213204551', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:51 GMT', 'Earliest 32 bit time - 1' ), + array( '20380119031408', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:08 GMT', 'Latest 32 bit time + 1' ), + array( '19011212000000', TS_MW, '19011212000000', 'Convert to itself r74778#c10645' ), + array( '19011213204551', TS_UNIX, '-2147483649', 'Earliest 32 bit unix time - 1' ), + array( '20380119031408', TS_UNIX, '2147483648', 'Latest 32 bit unix time + 1' ), + array( '-2147483649', TS_MW, '19011213204551', '1901 negative unix time to MediaWiki' ), + array( '-5331871504', TS_MW, '18010115123456', '1801 negative unix time to MediaWiki' ), + array( '0117-08-09 12:34:56', TS_RFC2822, 'Tue, 09 Aug 0117 12:34:56 GMT', 'Death of Roman Emperor [[Trajan]]' ), + + /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */ + array( '-58979923200', TS_RFC2822, 'Sun, 01 Jan 0101 00:00:00 GMT', '1/1/101' ), + array( '-62135596800', TS_RFC2822, 'Mon, 01 Jan 0001 00:00:00 GMT', 'Year 1' ), + + /* It is not clear if we should generate a year 0 or not + * We are completely off RFC2822 requirement of year being + * 1900 or later. + */ + array( '-62142076800', TS_RFC2822, 'Wed, 18 Oct 0000 00:00:00 GMT', 'ISO 8601:2004 [[year 0]], also called [[1 BC]]' ), + ); + } + + /** + * The Resource Loader uses wfTimestamp() to convert timestamps + * from If-Modified-Since header. Thus it must be able to parse all + * rfc2616 date formats + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1 + * @dataProvider provideHttpDates + */ + function testHttpDate( $input, $output, $desc ) { + $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc ); + } + + function provideHttpDates() { + return array( + array( 'Sun, 06 Nov 1994 08:49:37 GMT', '19941106084937', 'RFC 822 date' ), + array( 'Sunday, 06-Nov-94 08:49:37 GMT', '19941106084937', 'RFC 850 date' ), + array( 'Sun Nov 6 08:49:37 1994', '19941106084937', "ANSI C's asctime() format" ), + // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171 + array( 'Mon, 22 Nov 2010 14:12:42 GMT; length=52626', '20101122141242', 'Netscape extension to HTTP/1.0' ), + ); + } + + /** + * There are a number of assumptions in our codebase where wfTimestamp() + * should give the current date but it is not given a 0 there. See r71751 CR + */ + function testTimestampParameter() { + $now = wfTimestamp( TS_UNIX ); + // We check that wfTimestamp doesn't return false (error) and use a LessThan assert + // for the cases where the test is run in a second boundary. + + $zero = wfTimestamp( TS_UNIX, 0 ); + $this->assertNotEquals( false, $zero ); + $this->assertLessThan( 5, $zero - $now ); + + $empty = wfTimestamp( TS_UNIX, '' ); + $this->assertNotEquals( false, $empty ); + $this->assertLessThan( 5, $empty - $now ); + + $null = wfTimestamp( TS_UNIX, null ); + $this->assertNotEquals( false, $null ); + $this->assertLessThan( 5, $null - $now ); + } +} diff --git a/tests/phpunit/includes/HtmlTest.php b/tests/phpunit/includes/HtmlTest.php index 96bb1803..67b60d32 100644 --- a/tests/phpunit/includes/HtmlTest.php +++ b/tests/phpunit/includes/HtmlTest.php @@ -3,58 +3,90 @@ class HtmlTest extends MediaWikiTestCase { private static $oldLang; + private static $oldContLang; + private static $oldLanguageCode; + private static $oldNamespaces; public function setUp() { - global $wgLang, $wgLanguageCode; + global $wgLang, $wgContLang, $wgLanguageCode; self::$oldLang = $wgLang; + self::$oldContLang = $wgContLang; + self::$oldNamespaces = $wgContLang->getNamespaces(); + self::$oldLanguageCode = $wgLanguageCode; + $wgLanguageCode = 'en'; - $wgLang = Language::factory( $wgLanguageCode ); + $wgContLang = $wgLang = Language::factory( $wgLanguageCode ); + + // Hardcode namespaces during test runs, + // so that html output based on existing namespaces + // can be properly evaluated. + $wgContLang->setNamespaces( array( + -2 => 'Media', + -1 => 'Special', + 0 => '', + 1 => 'Talk', + 2 => 'User', + 3 => 'User_talk', + 4 => 'MyWiki', + 5 => 'MyWiki_Talk', + 6 => 'File', + 7 => 'File_talk', + 8 => 'MediaWiki', + 9 => 'MediaWiki_talk', + 10 => 'Template', + 11 => 'Template_talk', + 100 => 'Custom', + 101 => 'Custom_talk', + ) ); } public function tearDown() { - global $wgLang, $wgLanguageCode; + global $wgLang, $wgContLang, $wgLanguageCode; + + $wgContLang->setNamespaces( self::$oldNamespaces ); $wgLang = self::$oldLang; - $wgLanguageCode = $wgLang->getCode(); + $wgContLang = self::$oldContLang; + $wgLanguageCode = self::$oldLanguageCode; } public function testExpandAttributesSkipsNullAndFalse() { ### EMPTY ######## $this->AssertEmpty( - Html::expandAttributes( array( 'foo'=>null) ), + Html::expandAttributes( array( 'foo' => null ) ), 'skip keys with null value' ); $this->AssertEmpty( - Html::expandAttributes( array( 'foo'=>false) ), + Html::expandAttributes( array( 'foo' => false ) ), 'skip keys with false value' ); $this->AssertNotEmpty( - Html::expandAttributes( array( 'foo'=>'') ), + Html::expandAttributes( array( 'foo' => '' ) ), 'keep keys with an empty string' ); } public function testExpandAttributesForBooleans() { + global $wgHtml5; $this->AssertEquals( '', - Html::expandAttributes( array( 'selected'=>false) ), + Html::expandAttributes( array( 'selected' => false ) ), 'Boolean attributes do not generates output when value is false' ); $this->AssertEquals( '', - Html::expandAttributes( array( 'selected'=>null) ), + Html::expandAttributes( array( 'selected' => null ) ), 'Boolean attributes do not generates output when value is null' ); - ### FIXME: maybe they should just output 'selected' $this->AssertEquals( - ' selected=""', - Html::expandAttributes( array( 'selected'=>true ) ), + $wgHtml5 ? ' selected=""' : ' selected="selected"', + Html::expandAttributes( array( 'selected' => true ) ), 'Boolean attributes skip value output' ); $this->AssertEquals( - ' selected=""', + $wgHtml5 ? ' selected=""' : ' selected="selected"', Html::expandAttributes( array( 'selected' ) ), 'Boolean attributes (ex: selected) do not need a value' ); @@ -68,23 +100,234 @@ class HtmlTest extends MediaWikiTestCase { ### NOT EMPTY #### $this->AssertEquals( ' empty_string=""', - Html::expandAttributes( array( 'empty_string'=>'') ), + Html::expandAttributes( array( 'empty_string' => '' ) ), 'Value with an empty string' ); $this->AssertEquals( ' key="value"', - Html::expandAttributes( array( 'key'=>'value') ), + Html::expandAttributes( array( 'key' => 'value' ) ), 'Value is a string' ); $this->AssertEquals( ' one="1"', - Html::expandAttributes( array( 'one'=>1) ), + Html::expandAttributes( array( 'one' => 1 ) ), 'Value is a numeric one' ); $this->AssertEquals( ' zero="0"', - Html::expandAttributes( array( 'zero'=>0) ), + Html::expandAttributes( array( 'zero' => 0 ) ), 'Value is a numeric zero' ); } + + /** + * Html::expandAttributes has special features for HTML + * attributes that use space separated lists and also + * allows arrays to be used as values. + */ + public function testExpandAttributesListValueAttributes() { + ### STRING VALUES + $this->AssertEquals( + ' class="redundant spaces here"', + Html::expandAttributes( array( 'class' => ' redundant spaces here ' ) ), + 'Normalization should strip redundant spaces' + ); + $this->AssertEquals( + ' class="foo bar"', + Html::expandAttributes( array( 'class' => 'foo bar foo bar bar' ) ), + 'Normalization should remove duplicates in string-lists' + ); + ### "EMPTY" ARRAY VALUES + $this->AssertEquals( + ' class=""', + Html::expandAttributes( array( 'class' => array() ) ), + 'Value with an empty array' + ); + $this->AssertEquals( + ' class=""', + Html::expandAttributes( array( 'class' => array( null, '', ' ', ' ' ) ) ), + 'Array with null, empty string and spaces' + ); + ### NON-EMPTY ARRAY VALUES + $this->AssertEquals( + ' class="foo bar"', + Html::expandAttributes( array( 'class' => array( + 'foo', + 'bar', + 'foo', + 'bar', + 'bar', + ) ) ), + 'Normalization should remove duplicates in the array' + ); + $this->AssertEquals( + ' class="foo bar"', + Html::expandAttributes( array( 'class' => array( + 'foo bar', + 'bar foo', + 'foo', + 'bar bar', + ) ) ), + 'Normalization should remove duplicates in string-lists in the array' + ); + } + + /** + * Test feature added by r96188, let pass attributes values as + * a PHP array. Restricted to class,rel, accesskey. + */ + function testExpandAttributesSpaceSeparatedAttributesWithBoolean() { + $this->assertEquals( + ' class="booltrue one"', + Html::expandAttributes( array( 'class' => array( + 'booltrue' => true, + 'one' => 1, + + # Method use isset() internally, make sure we do discard + # attributes values which have been assigned well known values + 'emptystring' => '', + 'boolfalse' => false, + 'zero' => 0, + 'null' => null, + ))) + ); + } + + /** + * How do we handle duplicate keys in HTML attributes expansion? + * We could pass a "class" the values: 'GREEN' and array( 'GREEN' => false ) + * The later will take precedence. + * + * Feature added by r96188 + */ + function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() { + $this->assertEquals( + ' class=""', + Html::expandAttributes( array( 'class' => array( + 'GREEN', + 'GREEN' => false, + 'GREEN', + ))) + ); + } + + function testNamespaceSelector() { + $this->assertEquals( + '<select id="namespace" name="namespace">' . "\n" . +'<option value="0">(Main)</option>' . "\n" . +'<option value="1">Talk</option>' . "\n" . +'<option value="2">User</option>' . "\n" . +'<option value="3">User talk</option>' . "\n" . +'<option value="4">MyWiki</option>' . "\n" . +'<option value="5">MyWiki Talk</option>' . "\n" . +'<option value="6">File</option>' . "\n" . +'<option value="7">File talk</option>' . "\n" . +'<option value="8">MediaWiki</option>' . "\n" . +'<option value="9">MediaWiki talk</option>' . "\n" . +'<option value="10">Template</option>' . "\n" . +'<option value="11">Template talk</option>' . "\n" . +'<option value="100">Custom</option>' . "\n" . +'<option value="101">Custom talk</option>' . "\n" . +'</select>', + Html::namespaceSelector(), + 'Basic namespace selector without custom options' + ); + $this->assertEquals( + '<label for="mw-test-namespace">Select a namespace:</label> ' . +'<select id="mw-test-namespace" name="wpNamespace">' . "\n" . +'<option value="all">all</option>' . "\n" . +'<option value="0">(Main)</option>' . "\n" . +'<option value="1">Talk</option>' . "\n" . +'<option value="2" selected="">User</option>' . "\n" . +'<option value="3">User talk</option>' . "\n" . +'<option value="4">MyWiki</option>' . "\n" . +'<option value="5">MyWiki Talk</option>' . "\n" . +'<option value="6">File</option>' . "\n" . +'<option value="7">File talk</option>' . "\n" . +'<option value="8">MediaWiki</option>' . "\n" . +'<option value="9">MediaWiki talk</option>' . "\n" . +'<option value="10">Template</option>' . "\n" . +'<option value="11">Template talk</option>' . "\n" . +'<option value="100">Custom</option>' . "\n" . +'<option value="101">Custom talk</option>' . "\n" . +'</select>', + Html::namespaceSelector( + array( 'selected' => '2', 'all' => 'all', 'label' => 'Select a namespace:' ), + array( 'name' => 'wpNamespace', 'id' => 'mw-test-namespace' ) + ), + 'Basic namespace selector with custom values' + ); + } + + function testNamespaceSelectorIdAndNameDefaultsAttributes() { + + $this->assertNsSelectorIdAndName( + 'namespace', 'namespace', + Html::namespaceSelector( array(), array( + # neither 'id' nor 'name' key given + )), + "Neither 'id' nor 'name' key given" + ); + + $this->assertNsSelectorIdAndName( + 'namespace', 'select_name', + Html::namespaceSelector( array(), array( + 'name' => 'select_name', + # no 'id' key given + )), + "No 'id' key given, 'name' given" + ); + + $this->assertNsSelectorIdAndName( + 'select_id', 'namespace', + Html::namespaceSelector( array(), array( + 'id' => 'select_id', + # no 'name' key given + )), + "'id' given, no 'name' key given" + ); + + $this->assertNsSelectorIdAndName( + 'select_id', 'select_name', + Html::namespaceSelector( array(), array( + 'id' => 'select_id', + 'name' => 'select_name', + )), + "Both 'id' and 'name' given" + ); + } + + /** + * Helper to verify <select> attributes generated by Html::namespaceSelector() + * This helper expect the Html method to use 'namespace' as a default value for + * both 'id' and 'name' attributes. + * + * @param String $expectedId <select> id attribute value + * @param String $expectedName <select> name attribute value + * @param String $html Output of a call to Html::namespaceSelector() + * @param String $msg Optional message (default: '') + */ + function assertNsSelectorIdAndName( $expectedId, $expectedName, $html, $msg = '' ) { + $actualId = 'namespace'; + if( 1 === preg_match( '/id="(.+?)"/', $html, $m ) ) { + $actualId = $m[1]; + } + + $actualName = 'namespace'; + if( 1 === preg_match( '/name="(.+?)"/', $html, $m ) ) { + $actualName = $m[1]; + } + $this->assertEquals( + array( #expected + 'id' => $expectedId, + 'name' => $expectedName, + ), + array( #actual + 'id' => $actualId, + 'name' => $actualName, + ), + 'Html::namespaceSelector() got wrong id and/or name attribute(s). ' . $msg + ); + } + } diff --git a/tests/phpunit/includes/HttpTest.php b/tests/phpunit/includes/HttpTest.php index 1a99af7d..263383f1 100644 --- a/tests/phpunit/includes/HttpTest.php +++ b/tests/phpunit/includes/HttpTest.php @@ -1,325 +1,12 @@ <?php - -class MockCookie extends Cookie { - public function canServeDomain( $arg ) { return parent::canServeDomain( $arg ); } - public function canServePath( $arg ) { return parent::canServePath( $arg ); } - public function isUnExpired() { return parent::isUnExpired(); } -} - /** * @group Broken */ class HttpTest extends MediaWikiTestCase { - static $content; - static $headers; - static $has_curl; - static $has_fopen; - static $has_proxy = false; - static $proxy = "http://hulk:8080/"; - var $test_geturl = array( - "http://en.wikipedia.org/robots.txt", - "https://secure.wikimedia.org/", - "http://pecl.php.net/feeds/pkg_apc.rss", - "http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw", - "http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&format=php", - ); - var $test_requesturl = array( "http://en.wikipedia.org/wiki/Special:Export/User:MarkAHershberger" ); - - var $test_posturl = array( "http://www.comp.leeds.ac.uk/cgi-bin/Perl/environment-example" => "review=test" ); - - function setUp() { - putenv( "http_proxy" ); /* Remove any proxy env var, so curl doesn't get confused */ - if ( is_array( self::$content ) ) { - return; - } - self::$has_curl = function_exists( 'curl_init' ); - self::$has_fopen = wfIniGetBool( 'allow_url_fopen' ); - - if ( !file_exists( "/usr/bin/curl" ) ) { - $this->markTestIncomplete( "This test requires the curl binary at /usr/bin/curl. If you have curl, please file a bug on this test, or, better yet, provide a patch." ); - } - - $content = tempnam( wfTempDir(), "" ); - $headers = tempnam( wfTempDir(), "" ); - if ( !$content && !$headers ) { - die( "Couldn't create temp file!" ); - } - - // This probably isn't the best test for a proxy, but it works on my system! - system( "curl -0 -o $content -s " . self::$proxy ); - $out = file_get_contents( $content ); - if ( $out ) { - self::$has_proxy = true; - } - - /* Maybe use wget instead of curl here ... just to use a different codebase? */ - foreach ( $this->test_geturl as $u ) { - system( "curl -0 -s -D $headers '$u' -o $content" ); - self::$content["GET $u"] = file_get_contents( $content ); - self::$headers["GET $u"] = file_get_contents( $headers ); - } - foreach ( $this->test_requesturl as $u ) { - system( "curl -0 -s -X POST -H 'Content-Length: 0' -D $headers '$u' -o $content" ); - self::$content["POST $u"] = file_get_contents( $content ); - self::$headers["POST $u"] = file_get_contents( $headers ); - } - foreach ( $this->test_posturl as $u => $postData ) { - system( "curl -0 -s -X POST -d '$postData' -D $headers '$u' -o $content" ); - self::$content["POST $u => $postData"] = file_get_contents( $content ); - self::$headers["POST $u => $postData"] = file_get_contents( $headers ); - } - unlink( $content ); - unlink( $headers ); - } - - - function testInstantiation() { - Http::$httpEngine = false; - - $r = MWHttpRequest::factory( "http://www.example.com/" ); - if ( self::$has_curl ) { - $this->assertThat( $r, $this->isInstanceOf( 'CurlHttpRequest' ) ); - } else { - $this->assertThat( $r, $this->isInstanceOf( 'PhpHttpRequest' ) ); - } - unset( $r ); - - if ( !self::$has_fopen ) { - $this->setExpectedException( 'MWException' ); - } - Http::$httpEngine = 'php'; - $r = MWHttpRequest::factory( "http://www.example.com/" ); - $this->assertThat( $r, $this->isInstanceOf( 'PhpHttpRequest' ) ); - unset( $r ); - - if ( !self::$has_curl ) { - $this->setExpectedException( 'MWException' ); - } - Http::$httpEngine = 'curl'; - $r = MWHttpRequest::factory( "http://www.example.com/" ); - if ( self::$has_curl ) { - $this->assertThat( $r, $this->isInstanceOf( 'CurlHttpRequest' ) ); - } - } - - function runHTTPFailureChecks() { - // Each of the following requests should result in a failure. - - $timeout = 1; - $start_time = time(); - $r = Http::get( "http://www.example.com:1/", $timeout ); - $end_time = time(); - $this->assertLessThan( $timeout + 2, $end_time - $start_time, - "Request took less than {$timeout}s via " . Http::$httpEngine ); - $this->assertEquals( $r, false, "false -- what we get on error from Http::get()" ); - - $r = Http::get( "http://www.mediawiki.org/xml/made-up-url", $timeout ); - $this->assertFalse( $r, "False on 404s" ); - - - $r = MWHttpRequest::factory( "http://www.mediawiki.org/xml/made-up-url" ); - $er = $r->execute(); - if ( $r instanceof PhpHttpRequest && version_compare( '5.2.10', phpversion(), '>' ) ) { - $this->assertRegexp( "/HTTP request failed/", $er->getWikiText() ); - } else { - $this->assertRegexp( "/404 Not Found/", $er->getWikiText() ); - } - } - - function testFailureDefault() { - Http::$httpEngine = false; - $this->runHTTPFailureChecks(); - } - - function testFailurePhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = "php"; - $this->runHTTPFailureChecks(); - } - - function testFailureCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = "curl"; - $this->runHTTPFailureChecks(); - } - - /* ./phase3/includes/Import.php:1108: $data = Http::request( $method, $url ); */ - /* ./includes/Import.php:1124: $link = Title::newFromText( "$interwiki:Special:Export/$page" ); */ - /* ./includes/Import.php:1134: return ImportStreamSource::newFromURL( $url, "POST" ); */ - function runHTTPRequests( $proxy = null ) { - $opt = array(); - - if ( $proxy ) { - $opt['proxy'] = $proxy; - } elseif ( $proxy === false ) { - $opt['noProxy'] = true; - } - - /* no postData here because the only request I could find in code so far didn't have any */ - foreach ( $this->test_requesturl as $u ) { - $r = Http::request( "POST", $u, $opt ); - $this->assertEquals( self::$content["POST $u"], "$r", "POST $u with " . Http::$httpEngine ); - } - } - - function testRequestDefault() { - Http::$httpEngine = false; - $this->runHTTPRequests(); - } - - function testRequestPhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = "php"; - $this->runHTTPRequests(); - } - - function testRequestCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = "curl"; - $this->runHTTPRequests(); - } - - function runHTTPGets( $proxy = null ) { - $opt = array(); - - if ( $proxy ) { - $opt['proxy'] = $proxy; - } elseif ( $proxy === false ) { - $opt['noProxy'] = true; - } - - foreach ( $this->test_geturl as $u ) { - $r = Http::get( $u, 30, $opt ); /* timeout of 30s */ - $this->assertEquals( self::$content["GET $u"], "$r", "Get $u with " . Http::$httpEngine ); - } - } - - function testGetDefault() { - Http::$httpEngine = false; - $this->runHTTPGets(); - } - - function testGetPhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = "php"; - $this->runHTTPGets(); - } - - function testGetCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = "curl"; - $this->runHTTPGets(); - } - - function runHTTPPosts( $proxy = null ) { - $opt = array(); - - if ( $proxy ) { - $opt['proxy'] = $proxy; - } elseif ( $proxy === false ) { - $opt['noProxy'] = true; - } - - foreach ( $this->test_posturl as $u => $postData ) { - $opt['postData'] = $postData; - $r = Http::post( $u, $opt ); - $this->assertEquals( self::$content["POST $u => $postData"], "$r", - "POST $u (postData=$postData) with " . Http::$httpEngine ); - } - } - - function testPostDefault() { - Http::$httpEngine = false; - $this->runHTTPPosts(); - } - - function testPostPhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = "php"; - $this->runHTTPPosts(); - } - - function testPostCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = "curl"; - $this->runHTTPPosts(); - } - - function runProxyRequests() { - if ( !self::$has_proxy ) { - $this->markTestIncomplete( "This test requires a proxy." ); - } - $this->runHTTPGets( self::$proxy ); - $this->runHTTPPosts( self::$proxy ); - $this->runHTTPRequests( self::$proxy ); - - // Set false here to do noProxy - $this->runHTTPGets( false ); - $this->runHTTPPosts( false ); - $this->runHTTPRequests( false ); - } - - function testProxyDefault() { - Http::$httpEngine = false; - $this->runProxyRequests(); - } - - function testProxyPhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = 'php'; - $this->runProxyRequests(); - } - - function testProxyCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = 'curl'; - $this->runProxyRequests(); - } - - function testIsLocalUrl() { - } - - /* ./extensions/DonationInterface/payflowpro_gateway/payflowpro_gateway.body.php:559: $user_agent = Http::userAgent(); */ - function testUserAgent() { - } - - function testIsValidUrl() { - } - /** * @dataProvider cookieDomains */ - function testValidateCookieDomain( $expected, $domain, $origin=null ) { + function testValidateCookieDomain( $expected, $domain, $origin = null ) { if ( $origin ) { $ok = Cookie::validateCookieDomain( $domain, $origin ); $msg = "$domain against origin $origin"; @@ -329,7 +16,7 @@ class HttpTest extends MediaWikiTestCase { } $this->assertEquals( $expected, $ok, $msg ); } - + function cookieDomains() { return array( array( false, "org"), @@ -359,189 +46,11 @@ class HttpTest extends MediaWikiTestCase { ); } - function testSetCooke() { - $c = new MockCookie( "name", "value", - array( - "domain" => "ac.th", - "path" => "/path/", - ) ); - $this->assertFalse( $c->canServeDomain( "ac.th" ) ); - - $c = new MockCookie( "name", "value", - array( - "domain" => "example.com", - "path" => "/path/", - ) ); - - $this->assertTrue( $c->canServeDomain( "example.com" ) ); - $this->assertFalse( $c->canServeDomain( "www.example.com" ) ); - - $c = new MockCookie( "name", "value", - array( - "domain" => ".example.com", - "path" => "/path/", - ) ); - - $this->assertFalse( $c->canServeDomain( "www.example.net" ) ); - $this->assertFalse( $c->canServeDomain( "example.com" ) ); - $this->assertTrue( $c->canServeDomain( "www.example.com" ) ); - - $this->assertFalse( $c->canServePath( "/" ) ); - $this->assertFalse( $c->canServePath( "/bogus/path/" ) ); - $this->assertFalse( $c->canServePath( "/path" ) ); - $this->assertTrue( $c->canServePath( "/path/" ) ); - - $this->assertTrue( $c->isUnExpired() ); - - $this->assertEquals( "", $c->serializeToHttpRequest( "/path/", "www.example.net" ) ); - $this->assertEquals( "", $c->serializeToHttpRequest( "/", "www.example.com" ) ); - $this->assertEquals( "name=value", $c->serializeToHttpRequest( "/path/", "www.example.com" ) ); - - $c = new MockCookie( "name", "value", - array( - "domain" => "www.example.com", - "path" => "/path/", - ) ); - $this->assertFalse( $c->canServeDomain( "example.com" ) ); - $this->assertFalse( $c->canServeDomain( "www.example.net" ) ); - $this->assertTrue( $c->canServeDomain( "www.example.com" ) ); - - $c = new MockCookie( "name", "value", - array( - "domain" => ".example.com", - "path" => "/path/", - "expires" => "-1 day", - ) ); - $this->assertFalse( $c->isUnExpired() ); - $this->assertEquals( "", $c->serializeToHttpRequest( "/path/", "www.example.com" ) ); - - $c = new MockCookie( "name", "value", - array( - "domain" => ".example.com", - "path" => "/path/", - "expires" => "+1 day", - ) ); - $this->assertTrue( $c->isUnExpired() ); - $this->assertEquals( "name=value", $c->serializeToHttpRequest( "/path/", "www.example.com" ) ); - } - - function testCookieJarSetCookie() { - $cj = new CookieJar; - $cj->setCookie( "name", "value", - array( - "domain" => ".example.com", - "path" => "/path/", - ) ); - $cj->setCookie( "name2", "value", - array( - "domain" => ".example.com", - "path" => "/path/sub", - ) ); - $cj->setCookie( "name3", "value", - array( - "domain" => ".example.com", - "path" => "/", - ) ); - $cj->setCookie( "name4", "value", - array( - "domain" => ".example.net", - "path" => "/path/", - ) ); - $cj->setCookie( "name5", "value", - array( - "domain" => ".example.net", - "path" => "/path/", - "expires" => "-1 day", - ) ); - - $this->assertEquals( "name4=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) ); - $this->assertEquals( "name3=value", $cj->serializeToHttpRequest( "/", "www.example.com" ) ); - $this->assertEquals( "name=value; name3=value", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) ); - - $cj->setCookie( "name5", "value", - array( - "domain" => ".example.net", - "path" => "/path/", - "expires" => "+1 day", - ) ); - $this->assertEquals( "name4=value; name5=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) ); - - $cj->setCookie( "name4", "value", - array( - "domain" => ".example.net", - "path" => "/path/", - "expires" => "-1 day", - ) ); - $this->assertEquals( "name5=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) ); - } - - function testParseResponseHeader() { - $cj = new CookieJar; - - $h[] = "Set-Cookie: name4=value; domain=.example.com; path=/; expires=Mon, 09-Dec-2029 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[0], "www.example.com" ); - $this->assertEquals( "name4=value", $cj->serializeToHttpRequest( "/", "www.example.com" ) ); - - $h[] = "name4=value2; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[1], "www.example.com" ); - $this->assertEquals( "", $cj->serializeToHttpRequest( "/", "www.example.com" ) ); - $this->assertEquals( "name4=value2", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) ); - - $h[] = "name5=value3; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[2], "www.example.com" ); - $this->assertEquals( "name4=value2; name5=value3", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) ); - - $h[] = "name6=value3; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[3], "www.example.com" ); - $this->assertEquals( "", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) ); - - $h[] = "name6=value0; domain=.example.net; path=/path/; expires=Mon, 09-Dec-1999 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[4], "www.example.net" ); - $this->assertEquals( "", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) ); - - $h[] = "name6=value4; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[5], "www.example.net" ); - $this->assertEquals( "name6=value4", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) ); - } - - function runCookieRequests() { - $r = MWHttpRequest::factory( "http://www.php.net/manual", array( 'followRedirects' => true ) ); - $r->execute(); - - $jar = $r->getCookieJar(); - $this->assertThat( $jar, $this->isInstanceOf( 'CookieJar' ) ); - - $serialized = $jar->serializeToHttpRequest( "/search?q=test", "www.php.net" ); - $this->assertRegExp( '/\bCOUNTRY=[^=;]+/', $serialized ); - $this->assertRegExp( '/\bLAST_LANG=[^=;]+/', $serialized ); - $this->assertEquals( '', $jar->serializeToHttpRequest( "/search?q=test", "www.php.com" ) ); - } - - function testCookieRequestDefault() { - Http::$httpEngine = false; - $this->runCookieRequests(); - } - function testCookieRequestPhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = 'php'; - $this->runCookieRequests(); - } - function testCookieRequestCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = 'curl'; - $this->runCookieRequests(); - } - /** * Test Http::isValidURI() - * @bug 27854 : Http::isValidURI is to lax - *@dataProvider provideURI */ + * @bug 27854 : Http::isValidURI is too lax + * @dataProvider provideURI + */ function testIsValidUri( $expect, $URI, $message = '' ) { $this->assertEquals( $expect, @@ -567,7 +76,7 @@ class HttpTest extends MediaWikiTestCase { array( false, '\\host\directory', 'CIFS share' ), array( false, 'gopher://host/dir', 'Reject gopher scheme' ), array( false, 'telnet://host', 'Reject telnet scheme' ), - + # :\/\/ - double slashes array( false, 'http//example.org', 'Reject missing colon in protocol' ), array( false, 'http:/example.org', 'Reject missing slash in protocol' ), @@ -615,4 +124,57 @@ class HttpTest extends MediaWikiTestCase { ); } + /** + * Warning: + * + * These tests are for code that makes use of an artifact of how CURL + * handles header reporting on redirect pages, and will need to be + * rewritten when bug 29232 is taken care of (high-level handling of + * HTTP redirects). + */ + function testRelativeRedirections() { + $h = new MWHttpRequestTester( 'http://oldsite/file.ext' ); + # Forge a Location header + $h->setRespHeaders( 'location', array( + 'http://newsite/file.ext', + '/newfile.ext', + ) + ); + # Verify we correctly fix the Location + $this->assertEquals( + 'http://newsite/newfile.ext', + $h->getFinalUrl(), + "Relative file path Location: interpreted as full URL" + ); + + $h->setRespHeaders( 'location', array( + 'https://oldsite/file.ext' + ) + ); + $this->assertEquals( + 'https://oldsite/file.ext', + $h->getFinalUrl(), + "Location to the HTTPS version of the site" + ); + + $h->setRespHeaders( 'location', array( + '/anotherfile.ext', + 'http://anotherfile/hoster.ext', + 'https://anotherfile/hoster.ext' + ) + ); + $this->assertEquals( + 'https://anotherfile/hoster.ext', + $h->getFinalUrl( "Relative file path Location: should keep the latest host and scheme!") + ); + } +} + +/** + * Class to let us overwrite MWHttpREquest respHeaders variable + */ +class MWHttpRequestTester extends MWHttpRequest { + function setRespHeaders( $name, $value ) { + $this->respHeaders[$name] = $value ; + } } diff --git a/tests/phpunit/includes/IPTest.php b/tests/phpunit/includes/IPTest.php index c77dd852..4397b879 100644 --- a/tests/phpunit/includes/IPTest.php +++ b/tests/phpunit/includes/IPTest.php @@ -1,5 +1,5 @@ <?php -/* +/** * Tests for IP validity functions. Ported from /t/inc/IP.t by avar. */ @@ -43,20 +43,20 @@ class IPTest extends MediaWikiTestCase { $this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' ); $this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' ); $this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' ); - + $this->assertTrue( IP::isIPv6( 'fc:100::' ) ); $this->assertTrue( IP::isIPv6( 'fc:100:a::' ) ); $this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) ); $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) ); $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) ); $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) ); - + $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' ); $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ), 'IPv6 with 9 words ending with "::"' ); $this->assertFalse( IP::isIPv6( ':::' ) ); $this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' ); - + $this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' ); $this->assertTrue( IP::isIPv6( '::0' ) ); $this->assertTrue( IP::isIPv6( '::fc' ) ); @@ -66,14 +66,14 @@ class IPTest extends MediaWikiTestCase { $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) ); $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) ); $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) ); - + $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' ); $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' ); $this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' ); $this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' ); $this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' ); - + $this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' ); $this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' ); $this->assertTrue( IP::isIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' ) ); @@ -83,7 +83,7 @@ class IPTest extends MediaWikiTestCase { $this->assertTrue( IP::isIPv6( '2001::df'), 'IPv6 with "::" and 2 words' ); $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df'), 'IPv6 with "::" and 5 words' ); $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2'), 'IPv6 with "::" and 6 words' ); - + $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' ); $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' ); @@ -135,11 +135,11 @@ class IPTest extends MediaWikiTestCase { $this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' ); $this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' ); $this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' ); - + $this->assertTrue( IP::isValid( 'fc:100::' ) ); $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) ); $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) ); - + $this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' ); $this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' ); $this->assertTrue( IP::isValid( '2001::df'), 'IPv6 with "::" and 2 words' ); @@ -147,7 +147,7 @@ class IPTest extends MediaWikiTestCase { $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2'), 'IPv6 with "::" and 6 words' ); $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' ); $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' ); - + $this->assertFalse( IP::isValid( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' ); $this->assertFalse( IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ), 'IPv6 with 9 words ending with "::"' ); } @@ -276,7 +276,7 @@ class IPTest extends MediaWikiTestCase { foreach ( $private as $p ) { $this->assertFalse( IP::isPublic( $p ), "$p is not a public IP address" ); } - $public = array( '2001:5c0:1000:a::133', 'fc::3' ); + $public = array( '2001:5c0:1000:a::133', 'fc::3', '00FC::' ); foreach ( $public as $p ) { $this->assertTrue( IP::isPublic( $p ), "$p is a public IP address" ); } @@ -332,7 +332,7 @@ class IPTest extends MediaWikiTestCase { $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', IP::hexToOctet( 'FCCFFAFF' ) ); } - /* + /** * IP::parseCIDR() returns an array containing a signed IP address * representing the network mask and the bit mask. * @covers IP::parseCIDR @@ -391,7 +391,7 @@ class IPTest extends MediaWikiTestCase { } /** - * Issues there are most probably from IP::toHex() or IP::parseRange() + * Issues there are most probably from IP::toHex() or IP::parseRange() * @covers IP::isInRange * @dataProvider provideIPsAndRanges */ @@ -464,9 +464,9 @@ class IPTest extends MediaWikiTestCase { */ function testCombineHostAndPort( $expected, $input, $description ) { list( $host, $port, $defaultPort ) = $input; - $this->assertEquals( - $expected, - IP::combineHostAndPort( $host, $port, $defaultPort ), + $this->assertEquals( + $expected, + IP::combineHostAndPort( $host, $port, $defaultPort ), $description ); } diff --git a/tests/phpunit/includes/LocalFileTest.php b/tests/phpunit/includes/LocalFileTest.php index e08d4d7e..5b26b89c 100644 --- a/tests/phpunit/includes/LocalFileTest.php +++ b/tests/phpunit/includes/LocalFileTest.php @@ -10,12 +10,21 @@ class LocalFileTest extends MediaWikiTestCase { global $wgCapitalLinks; $wgCapitalLinks = true; + $info = array( - 'name' => 'test', - 'directory' => '/testdir', - 'url' => '/testurl', - 'hashLevels' => 2, + 'name' => 'test', + 'directory' => '/testdir', + 'url' => '/testurl', + 'hashLevels' => 2, 'transformVia404' => false, + 'backend' => new FSFileBackend( array( + 'name' => 'local-backend', + 'lockManager' => 'fsLockManager', + 'containerPaths' => array( + 'cont1' => "/testdir/local-backend/tempimages/cont1", + 'cont2' => "/testdir/local-backend/tempimages/cont2" + ) + ) ) ); $this->repo_hl0 = new LocalRepo( array( 'hashLevels' => 0 ) + $info ); $this->repo_hl2 = new LocalRepo( array( 'hashLevels' => 2 ) + $info ); @@ -44,17 +53,17 @@ class LocalFileTest extends MediaWikiTestCase { } function testGetArchivePath() { - $this->assertEquals( '/testdir/archive', $this->file_hl0->getArchivePath() ); - $this->assertEquals( '/testdir/archive/a/a2', $this->file_hl2->getArchivePath() ); - $this->assertEquals( '/testdir/archive/!', $this->file_hl0->getArchivePath( '!' ) ); - $this->assertEquals( '/testdir/archive/a/a2/!', $this->file_hl2->getArchivePath( '!' ) ); + $this->assertEquals( 'mwstore://local-backend/test-public/archive', $this->file_hl0->getArchivePath() ); + $this->assertEquals( 'mwstore://local-backend/test-public/archive/a/a2', $this->file_hl2->getArchivePath() ); + $this->assertEquals( 'mwstore://local-backend/test-public/archive/!', $this->file_hl0->getArchivePath( '!' ) ); + $this->assertEquals( 'mwstore://local-backend/test-public/archive/a/a2/!', $this->file_hl2->getArchivePath( '!' ) ); } function testGetThumbPath() { - $this->assertEquals( '/testdir/thumb/Test!', $this->file_hl0->getThumbPath() ); - $this->assertEquals( '/testdir/thumb/a/a2/Test!', $this->file_hl2->getThumbPath() ); - $this->assertEquals( '/testdir/thumb/Test!/x', $this->file_hl0->getThumbPath( 'x' ) ); - $this->assertEquals( '/testdir/thumb/a/a2/Test!/x', $this->file_hl2->getThumbPath( 'x' ) ); + $this->assertEquals( 'mwstore://local-backend/test-thumb/Test!', $this->file_hl0->getThumbPath() ); + $this->assertEquals( 'mwstore://local-backend/test-thumb/a/a2/Test!', $this->file_hl2->getThumbPath() ); + $this->assertEquals( 'mwstore://local-backend/test-thumb/Test!/x', $this->file_hl0->getThumbPath( 'x' ) ); + $this->assertEquals( 'mwstore://local-backend/test-thumb/a/a2/Test!/x', $this->file_hl2->getThumbPath( 'x' ) ); } function testGetArchiveUrl() { diff --git a/tests/phpunit/includes/MWNamespaceTest.php b/tests/phpunit/includes/MWNamespaceTest.php index 462afc24..6b231fc5 100644 --- a/tests/phpunit/includes/MWNamespaceTest.php +++ b/tests/phpunit/includes/MWNamespaceTest.php @@ -1,7 +1,7 @@ <?php /** - * @author Ashar Voultoiz - * @copyright Copyright © 2011, Ashar Voultoiz + * @author Antoine Musso + * @copyright Copyright © 2011, Antoine Musso * @file */ @@ -39,40 +39,55 @@ class MWNamespaceTest extends MediaWikiTestCase { /** * Please make sure to change testIsTalk() if you change the assertions below */ - public function testIsMain() { + public function testIsSubject() { // Special namespaces - $this->assertTrue( MWNamespace::isMain( NS_MEDIA ) ); - $this->assertTrue( MWNamespace::isMain( NS_SPECIAL ) ); + $this->assertIsSubject( NS_MEDIA ); + $this->assertIsSubject( NS_SPECIAL ); // Subject pages - $this->assertTrue( MWNamespace::isMain( NS_MAIN ) ); - $this->assertTrue( MWNamespace::isMain( NS_USER ) ); - $this->assertTrue( MWNamespace::isMain( 100 ) ); # user defined + $this->assertIsSubject( NS_MAIN ); + $this->assertIsSubject( NS_USER ); + $this->assertIsSubject( 100 ); # user defined // Talk pages - $this->assertFalse( MWNamespace::isMain( NS_TALK ) ); - $this->assertFalse( MWNamespace::isMain( NS_USER_TALK ) ); - $this->assertFalse( MWNamespace::isMain( 101 ) ); # user defined + $this->assertIsNotSubject( NS_TALK ); + $this->assertIsNotSubject( NS_USER_TALK ); + $this->assertIsNotSubject( 101 ); # user defined + + // Back compat + $this->assertTrue( MWNamespace::isMain( NS_MAIN ) == MWNamespace::isSubject( NS_MAIN ) ); + $this->assertTrue( MWNamespace::isMain( NS_USER_TALK ) == MWNamespace::isSubject( NS_USER_TALK ) ); } /** - * Reverse of testIsMain(). - * Please update testIsMain() if you change assertions below + * Reverse of testIsSubject(). + * Please update testIsSubject() if you change assertions below */ public function testIsTalk() { // Special namespaces - $this->assertFalse( MWNamespace::isTalk( NS_MEDIA ) ); - $this->assertFalse( MWNamespace::isTalk( NS_SPECIAL ) ); + $this->assertIsNotTalk( NS_MEDIA ); + $this->assertIsNotTalk( NS_SPECIAL ); // Subject pages - $this->assertFalse( MWNamespace::isTalk( NS_MAIN ) ); - $this->assertFalse( MWNamespace::isTalk( NS_USER ) ); - $this->assertFalse( MWNamespace::isTalk( 100 ) ); # user defined + $this->assertIsNotTalk( NS_MAIN ); + $this->assertIsNotTalk( NS_USER ); + $this->assertIsNotTalk( 100 ); # user defined // Talk pages - $this->assertTrue( MWNamespace::isTalk( NS_TALK ) ); - $this->assertTrue( MWNamespace::isTalk( NS_USER_TALK ) ); - $this->assertTrue( MWNamespace::isTalk( 101 ) ); # user defined + $this->assertIsTalk( NS_TALK ); + $this->assertIsTalk( NS_USER_TALK ); + $this->assertIsTalk( 101 ); # user defined + } + + /** + */ + public function testGetSubject() { + // Special namespaces are their own subjects + $this->assertEquals( NS_MEDIA, MWNamespace::getSubject( NS_MEDIA ) ); + $this->assertEquals( NS_SPECIAL, MWNamespace::getSubject( NS_SPECIAL ) ); + + $this->assertEquals( NS_MAIN, MWNamespace::getSubject( NS_TALK ) ); + $this->assertEquals( NS_USER, MWNamespace::getSubject( NS_USER_TALK ) ); } /** @@ -82,6 +97,9 @@ class MWNamespaceTest extends MediaWikiTestCase { */ public function testGetTalk() { $this->assertEquals( NS_TALK, MWNamespace::getTalk( NS_MAIN ) ); + $this->assertEquals( NS_TALK, MWNamespace::getTalk( NS_TALK ) ); + $this->assertEquals( NS_USER_TALK, MWNamespace::getTalk( NS_USER ) ); + $this->assertEquals( NS_USER_TALK, MWNamespace::getTalk( NS_USER_TALK ) ); } /** @@ -93,7 +111,7 @@ class MWNamespaceTest extends MediaWikiTestCase { $this->assertNull( MWNamespace::getTalk( NS_MEDIA ) ); } - /** + /** * Exceptions with getTalk() * NS_SPECIAL does not have talk pages. MediaWiki raise an exception for them. * @expectedException MWException @@ -108,7 +126,7 @@ class MWNamespaceTest extends MediaWikiTestCase { * the function testGetAssociatedExceptions() */ public function testGetAssociated() { - $this->assertEquals( NS_TALK, MWNamespace::getAssociated( NS_MAIN ) ); + $this->assertEquals( NS_TALK, MWNamespace::getAssociated( NS_MAIN ) ); $this->assertEquals( NS_MAIN, MWNamespace::getAssociated( NS_TALK ) ); } @@ -131,80 +149,122 @@ class MWNamespaceTest extends MediaWikiTestCase { } /** - */ - public function testGetSubject() { - // Special namespaces are their own subjects - $this->assertEquals( NS_MEDIA, MWNamespace::getSubject( NS_MEDIA ) ); - $this->assertEquals( NS_SPECIAL, MWNamespace::getSubject( NS_SPECIAL ) ); - - $this->assertEquals( NS_MAIN, MWNamespace::getSubject( NS_TALK ) ); - $this->assertEquals( NS_USER, MWNamespace::getSubject( NS_USER_TALK ) ); - } - - /** * @todo Implement testExists(). */ +/* public function testExists() { // Remove the following lines when you implement this test. $this->markTestIncomplete( 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' ); } +*/ + + /** + * Test MWNamespace::equals + * Note if we add a namespace registration system with keys like 'MAIN' + * we should add tests here for equivilance on things like 'MAIN' == 0 + * and 'MAIN' == NS_MAIN. + */ + public function testEquals() { + $this->assertTrue( MWNamespace::equals( NS_MAIN, NS_MAIN ) ); + $this->assertTrue( MWNamespace::equals( NS_MAIN, 0 ) ); // In case we make NS_MAIN 'MAIN' + $this->assertTrue( MWNamespace::equals( NS_USER, NS_USER ) ); + $this->assertTrue( MWNamespace::equals( NS_USER, 2 ) ); + $this->assertTrue( MWNamespace::equals( NS_USER_TALK, NS_USER_TALK ) ); + $this->assertTrue( MWNamespace::equals( NS_SPECIAL, NS_SPECIAL ) ); + $this->assertFalse( MWNamespace::equals( NS_MAIN, NS_TALK ) ); + $this->assertFalse( MWNamespace::equals( NS_USER, NS_USER_TALK ) ); + $this->assertFalse( MWNamespace::equals( NS_PROJECT, NS_TEMPLATE ) ); + } + + /** + * Test MWNamespace::subjectEquals + */ + public function testSubjectEquals() { + $this->assertSameSubject( NS_MAIN, NS_MAIN ); + $this->assertSameSubject( NS_MAIN, 0 ); // In case we make NS_MAIN 'MAIN' + $this->assertSameSubject( NS_USER, NS_USER ); + $this->assertSameSubject( NS_USER, 2 ); + $this->assertSameSubject( NS_USER_TALK, NS_USER_TALK ); + $this->assertSameSubject( NS_SPECIAL, NS_SPECIAL ); + $this->assertSameSubject( NS_MAIN, NS_TALK ); + $this->assertSameSubject( NS_USER, NS_USER_TALK ); + + $this->assertDifferentSubject( NS_PROJECT, NS_TEMPLATE ); + $this->assertDifferentSubject( NS_SPECIAL, NS_MAIN ); + } + + public function testSpecialAndMediaAreDifferentSubjects() { + $this->assertDifferentSubject( + NS_MEDIA, NS_SPECIAL, + "NS_MEDIA and NS_SPECIAL are different subject namespaces" + ); + $this->assertDifferentSubject( + NS_SPECIAL, NS_MEDIA, + "NS_SPECIAL and NS_MEDIA are different subject namespaces" + ); + + } /** * @todo Implement testGetCanonicalNamespaces(). */ +/* public function testGetCanonicalNamespaces() { // Remove the following lines when you implement this test. $this->markTestIncomplete( 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' ); } - +*/ /** * @todo Implement testGetCanonicalName(). */ +/* public function testGetCanonicalName() { // Remove the following lines when you implement this test. $this->markTestIncomplete( 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' ); } - +*/ /** * @todo Implement testGetCanonicalIndex(). */ +/* public function testGetCanonicalIndex() { // Remove the following lines when you implement this test. $this->markTestIncomplete( 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' ); } - +*/ /** * @todo Implement testGetValidNamespaces(). */ +/* public function testGetValidNamespaces() { // Remove the following lines when you implement this test. $this->markTestIncomplete( 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' ); } - +*/ /** */ public function testCanTalk() { - $this->assertFalse( MWNamespace::canTalk( NS_MEDIA ) ); - $this->assertFalse( MWNamespace::canTalk( NS_SPECIAL ) ); + $this->assertCanNotTalk( NS_MEDIA ); + $this->assertCanNotTalk( NS_SPECIAL ); - $this->assertTrue( MWNamespace::canTalk( NS_MAIN ) ); - $this->assertTrue( MWNamespace::canTalk( NS_TALK ) ); - $this->assertTrue( MWNamespace::canTalk( NS_USER ) ); - $this->assertTrue( MWNamespace::canTalk( NS_USER_TALK ) ); + $this->assertCanTalk( NS_MAIN ); + $this->assertCanTalk( NS_TALK ); + $this->assertCanTalk( NS_USER ); + $this->assertCanTalk( NS_USER_TALK ); // User defined namespaces - $this->assertTrue( MWNamespace::canTalk( 100 ) ); - $this->assertTrue( MWNamespace::canTalk( 101 ) ); + $this->assertCanTalk( 100 ); + $this->assertCanTalk( 101 ); } /** @@ -212,16 +272,47 @@ class MWNamespaceTest extends MediaWikiTestCase { public function testIsContent() { // NS_MAIN is a content namespace per DefaultSettings.php // and per function definition. - $this->assertTrue( MWNamespace::isContent( NS_MAIN ) ); + $this->assertIsContent( NS_MAIN ); + + global $wgContentNamespaces; + + $saved = $wgContentNamespaces; + + $wgContentNamespaces[] = NS_MAIN; + $this->assertIsContent( NS_MAIN ); // Other namespaces which are not expected to be content - $this->assertFalse( MWNamespace::isContent( NS_MEDIA ) ); - $this->assertFalse( MWNamespace::isContent( NS_SPECIAL ) ); - $this->assertFalse( MWNamespace::isContent( NS_TALK ) ); - $this->assertFalse( MWNamespace::isContent( NS_USER ) ); - $this->assertFalse( MWNamespace::isContent( NS_CATEGORY ) ); - // User defined namespace: - $this->assertFalse( MWNamespace::isContent( 100 ) ); + if ( isset( $wgContentNamespaces[NS_MEDIA] ) ) { + unset( $wgContentNamespaces[NS_MEDIA] ); + } + $this->assertIsNotContent( NS_MEDIA ); + + if ( isset( $wgContentNamespaces[NS_SPECIAL] ) ) { + unset( $wgContentNamespaces[NS_SPECIAL] ); + } + $this->assertIsNotContent( NS_SPECIAL ); + + if ( isset( $wgContentNamespaces[NS_TALK] ) ) { + unset( $wgContentNamespaces[NS_TALK] ); + } + $this->assertIsNotContent( NS_TALK ); + + if ( isset( $wgContentNamespaces[NS_USER] ) ) { + unset( $wgContentNamespaces[NS_USER] ); + } + $this->assertIsNotContent( NS_USER ); + + if ( isset( $wgContentNamespaces[NS_CATEGORY] ) ) { + unset( $wgContentNamespaces[NS_CATEGORY] ); + } + $this->assertIsNotContent( NS_CATEGORY ); + + if ( isset( $wgContentNamespaces[100] ) ) { + unset( $wgContentNamespaces[100] ); + } + $this->assertIsNotContent( 100 ); + + $wgContentNamespaces = $saved; } /** @@ -231,47 +322,47 @@ class MWNamespaceTest extends MediaWikiTestCase { public function testIsContentWithAdditionsInWgContentNamespaces() { // NS_MAIN is a content namespace per DefaultSettings.php // and per function definition. - $this->assertTrue( MWNamespace::isContent( NS_MAIN ) ); + $this->assertIsContent( NS_MAIN ); // Tests that user defined namespace #252 is not content: - $this->assertFalse( MWNamespace::isContent( 252 ) ); + $this->assertIsNotContent( 252 ); # @todo FIXME: Is global saving really required for PHPUnit? // Bless namespace # 252 as a content namespace global $wgContentNamespaces; $savedGlobal = $wgContentNamespaces; $wgContentNamespaces[] = 252; - $this->assertTrue( MWNamespace::isContent( 252 ) ); + $this->assertIsContent( 252 ); // Makes sure NS_MAIN was not impacted - $this->assertTrue( MWNamespace::isContent( NS_MAIN ) ); + $this->assertIsContent( NS_MAIN ); // Restore global $wgContentNamespaces = $savedGlobal; // Verify namespaces after global restauration - $this->assertTrue( MWNamespace::isContent( NS_MAIN ) ); - $this->assertFalse( MWNamespace::isContent( 252 ) ); + $this->assertIsContent( NS_MAIN ); + $this->assertIsNotContent( 252 ); } public function testIsWatchable() { // Specials namespaces are not watchable - $this->assertFalse( MWNamespace::isWatchable( NS_MEDIA ) ); - $this->assertFalse( MWNamespace::isWatchable( NS_SPECIAL ) ); + $this->assertIsNotWatchable( NS_MEDIA ); + $this->assertIsNotWatchable( NS_SPECIAL ); // Core defined namespaces are watchables - $this->assertTrue( MWNamespace::isWatchable( NS_MAIN ) ); - $this->assertTrue( MWNamespace::isWatchable( NS_TALK ) ); + $this->assertIsWatchable( NS_MAIN ); + $this->assertIsWatchable( NS_TALK ); // Additional, user defined namespaces are watchables - $this->assertTrue( MWNamespace::isWatchable( 100 ) ); - $this->assertTrue( MWNamespace::isWatchable( 101 ) ); + $this->assertIsWatchable( 100 ); + $this->assertIsWatchable( 101 ); } public function testHasSubpages() { // Special namespaces: - $this->assertFalse( MWNamespace::hasSubpages( NS_MEDIA ) ); - $this->assertFalse( MWNamespace::hasSubpages( NS_SPECIAL ) ); + $this->assertHasNotSubpages( NS_MEDIA ); + $this->assertHasNotSubpages( NS_SPECIAL ); // namespaces without subpages # save up global @@ -282,12 +373,12 @@ class MWNamespaceTest extends MediaWikiTestCase { unset( $wgNamespacesWithSubpages[NS_MAIN] ); } - $this->assertFalse( MWNamespace::hasSubpages( NS_MAIN ) ); + $this->assertHasNotSubpages( NS_MAIN ); $wgNamespacesWithSubpages[NS_MAIN] = true; - $this->assertTrue( MWNamespace::hasSubpages( NS_MAIN ) ); + $this->assertHasSubpages( NS_MAIN ); $wgNamespacesWithSubpages[NS_MAIN] = false; - $this->assertFalse( MWNamespace::hasSubpages( NS_MAIN ) ); + $this->assertHasNotSubpages( NS_MAIN ); # restore global if( $saved !== null ) { @@ -295,9 +386,9 @@ class MWNamespaceTest extends MediaWikiTestCase { } // Some namespaces with subpages - $this->assertTrue( MWNamespace::hasSubpages( NS_TALK ) ); - $this->assertTrue( MWNamespace::hasSubpages( NS_USER ) ); - $this->assertTrue( MWNamespace::hasSubpages( NS_USER_TALK ) ); + $this->assertHasSubpages( NS_TALK ); + $this->assertHasSubpages( NS_USER ); + $this->assertHasSubpages( NS_USER_TALK ); } /** @@ -311,6 +402,7 @@ class MWNamespaceTest extends MediaWikiTestCase { global $wgContentNamespaces; + $saved = $wgContentNamespaces; # test !is_array( $wgcontentNamespaces ) $wgContentNamespaces = ''; $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() ); @@ -330,7 +422,7 @@ class MWNamespaceTest extends MediaWikiTestCase { $this->assertEquals( array( NS_MAIN, NS_USER, NS_CATEGORY ), MWNamespace::getcontentNamespaces(), - 'NS_MAIN is forced in wgContentNamespaces even if unwanted' + 'NS_MAIN is forced in $wgContentNamespaces even if unwanted' ); # test other cases, return $wgcontentNamespaces as is @@ -346,6 +438,7 @@ class MWNamespaceTest extends MediaWikiTestCase { MWNamespace::getcontentNamespaces() ); + $wgContentNamespaces = $saved; } /** @@ -361,14 +454,14 @@ class MWNamespaceTest extends MediaWikiTestCase { ); // Boths are capitalized by default - $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIA ) ); - $this->assertTrue( MWNamespace::isCapitalized( NS_FILE ) ); + $this->assertIsCapitalized( NS_MEDIA ); + $this->assertIsCapitalized( NS_FILE ); // Always capitalized namespaces // @see MWNamespace::$alwaysCapitalizedNamespaces - $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) ); - $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) ); - $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) ); + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_MEDIAWIKI ); } /** @@ -389,17 +482,17 @@ class MWNamespaceTest extends MediaWikiTestCase { $savedGlobal = $wgCapitalLinks; $wgCapitalLinks = true; - $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) ); - $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT_TALK ) ); + $this->assertIsCapitalized( NS_PROJECT ); + $this->assertIsCapitalized( NS_PROJECT_TALK ); $wgCapitalLinks = false; // hardcoded namespaces (see above function) are still capitalized: - $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) ); - $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) ); - $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) ); + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_MEDIAWIKI ); // setting is correctly applied - $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT ) ); - $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT_TALK ) ); + $this->assertIsNotCapitalized( NS_PROJECT ); + $this->assertIsNotCapitalized( NS_PROJECT_TALK ); // reset global state: $wgCapitalLinks = $savedGlobal; @@ -417,28 +510,28 @@ class MWNamespaceTest extends MediaWikiTestCase { $savedGlobal = $wgCapitalLinkOverrides; // Test default settings - $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) ); - $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT_TALK ) ); + $this->assertIsCapitalized( NS_PROJECT ); + $this->assertIsCapitalized( NS_PROJECT_TALK ); // hardcoded namespaces (see above function) are capitalized: - $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) ); - $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) ); - $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) ); + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_MEDIAWIKI ); // Hardcoded namespaces remains capitalized $wgCapitalLinkOverrides[NS_SPECIAL] = false; $wgCapitalLinkOverrides[NS_USER] = false; $wgCapitalLinkOverrides[NS_MEDIAWIKI] = false; - $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) ); - $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) ); - $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) ); + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_MEDIAWIKI ); $wgCapitalLinkOverrides = $savedGlobal; $wgCapitalLinkOverrides[NS_PROJECT] = false; - $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT ) ); + $this->assertIsNotCapitalized( NS_PROJECT ); $wgCapitalLinkOverrides[NS_PROJECT] = true ; - $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) ); + $this->assertIsCapitalized( NS_PROJECT ); unset( $wgCapitalLinkOverrides[NS_PROJECT] ); - $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) ); + $this->assertIsCapitalized( NS_PROJECT ); // reset global state: $wgCapitalLinkOverrides = $savedGlobal; @@ -456,5 +549,45 @@ class MWNamespaceTest extends MediaWikiTestCase { $this->assertFalse( MWNamespace::hasGenderDistinction( NS_TALK ) ); } + + ####### HELPERS ########################################################### + function __call( $method, $args ) { + // Call the real method if it exists + if( method_exists($this, $method ) ) { + return $this->$method( $args ); + } + + if( preg_match( '/^assert(Has|Is|Can)(Not|)(Subject|Talk|Watchable|Content|Subpages|Capitalized)$/', $method, $m ) ) { + # Interprets arguments: + $ns = $args[0]; + $msg = isset($args[1]) ? $args[1] : " dummy message"; + + # Forge the namespace constant name: + if( $ns === 0 ) { + $ns_name = "NS_MAIN"; + } else { + $ns_name = "NS_" . strtoupper( MWNamespace::getCanonicalName( $ns ) ); + } + # ... and the MWNamespace method name + $nsMethod = strtolower( $m[1] ) . $m[3]; + + $expect = ($m[2] === ''); + $expect_name = $expect ? 'TRUE' : 'FALSE'; + + return $this->assertEquals( $expect, + MWNamespace::$nsMethod( $ns, $msg ), + "MWNamespace::$nsMethod( $ns_name ) should returns $expect_name" + ); + } + + throw new Exception( __METHOD__ . " could not find a method named $method\n" ); + } + + function assertSameSubject( $ns1, $ns2, $msg = '' ) { + $this->assertTrue( MWNamespace::subjectEquals( $ns1, $ns2, $msg ) ); + } + function assertDifferentSubject( $ns1, $ns2, $msg = '' ) { + $this->assertFalse( MWNamespace::subjectEquals( $ns1, $ns2, $msg ) ); + } } diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php index 45c02bbe..295b6d74 100644 --- a/tests/phpunit/includes/MessageTest.php +++ b/tests/phpunit/includes/MessageTest.php @@ -47,7 +47,7 @@ class MessageTest extends MediaWikiLangTestCase { $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg disabled' ); $wgForceUIMsgAsContentMsg['testInContentLanguage'] = 'mainpage'; $this->assertEquals( 'Accueil', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg enabled' ); - + /* Restore globals */ $wgLang = $oldLang; unset( $wgForceUIMsgAsContentMsg['testInContentLanguage'] ); diff --git a/tests/phpunit/includes/ParserOptionsTest.php b/tests/phpunit/includes/ParserOptionsTest.php index 58c89146..59c955fe 100644 --- a/tests/phpunit/includes/ParserOptionsTest.php +++ b/tests/phpunit/includes/ParserOptionsTest.php @@ -6,10 +6,9 @@ class ParserOptionsTest extends MediaWikiTestCase { private $pcache; function setUp() { - ParserTest::setUp(); //reuse setup from parser tests global $wgContLang, $wgUser, $wgLanguageCode; $wgContLang = Language::factory( $wgLanguageCode ); - $this->popts = new ParserOptions( $wgUser ); + $this->popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang ); $this->pcache = ParserCache::singleton(); } @@ -26,11 +25,11 @@ class ParserOptionsTest extends MediaWikiTestCase { $wgUseDynamicDates = true; $title = Title::newFromText( "Some test article" ); - $article = new Article( $title ); + $page = WikiPage::factory( $title ); - $pcacheKeyBefore = $this->pcache->getKey( $article, $this->popts ); + $pcacheKeyBefore = $this->pcache->getKey( $page, $this->popts ); $this->assertNotNull( $this->popts->getDateFormat() ); - $pcacheKeyAfter = $this->pcache->getKey( $article, $this->popts ); + $pcacheKeyAfter = $this->pcache->getKey( $page, $this->popts ); $this->assertEquals( $pcacheKeyBefore, $pcacheKeyAfter ); } } diff --git a/tests/phpunit/includes/PathRouterTest.php b/tests/phpunit/includes/PathRouterTest.php new file mode 100644 index 00000000..f6274584 --- /dev/null +++ b/tests/phpunit/includes/PathRouterTest.php @@ -0,0 +1,254 @@ +<?php +/** + * Tests for the PathRouter parsing + */ + +class PathRouterTest extends MediaWikiTestCase { + + public function setUp() { + $router = new PathRouter; + $router->add("/wiki/$1"); + $this->basicRouter = $router; + } + + /** + * Test basic path parsing + */ + public function testBasic() { + $matches = $this->basicRouter->parse( "/wiki/Foo" ); + $this->assertEquals( $matches, array( 'title' => "Foo" ) ); + } + + /** + * Test loose path auto-$1 + */ + public function testLoose() { + $router = new PathRouter; + $router->add("/"); # Should be the same as "/$1" + $matches = $router->parse( "/Foo" ); + $this->assertEquals( $matches, array( 'title' => "Foo" ) ); + + $router = new PathRouter; + $router->add("/wiki"); # Should be the same as /wiki/$1 + $matches = $router->parse( "/wiki/Foo" ); + $this->assertEquals( $matches, array( 'title' => "Foo" ) ); + + $router = new PathRouter; + $router->add("/wiki/"); # Should be the same as /wiki/$1 + $matches = $router->parse( "/wiki/Foo" ); + $this->assertEquals( $matches, array( 'title' => "Foo" ) ); + } + + /** + * Test to ensure that path is based on specifity, not order + */ + public function testOrder() { + $router = new PathRouter; + $router->add("/$1"); + $router->add("/a/$1"); + $router->add("/b/$1"); + $matches = $router->parse( "/a/Foo" ); + $this->assertEquals( $matches, array( 'title' => "Foo" ) ); + + $router = new PathRouter; + $router->add("/b/$1"); + $router->add("/a/$1"); + $router->add("/$1"); + $matches = $router->parse( "/a/Foo" ); + $this->assertEquals( $matches, array( 'title' => "Foo" ) ); + } + + /** + * Test the handling of key based arrays with a url parameter + */ + public function testKeyParameter() { + $router = new PathRouter; + $router->add( array( 'edit' => "/edit/$1" ), array( 'action' => '$key' ) ); + $matches = $router->parse( "/edit/Foo" ); + $this->assertEquals( $matches, array( 'title' => "Foo", 'action' => 'edit' ) ); + } + + /** + * Test the handling of $2 inside paths + */ + public function testAdditionalParameter() { + // Basic $2 + $router = new PathRouter; + $router->add( '/$2/$1', array( 'test' => '$2' ) ); + $matches = $router->parse( "/asdf/Foo" ); + $this->assertEquals( $matches, array( 'title' => "Foo", 'test' => 'asdf' ) ); + } + + /** + * Test additional restricted value parameter + */ + public function testRestrictedValue() { + $router = new PathRouter; + $router->add( '/$2/$1', + array( 'test' => '$2' ), + array( '$2' => array( 'a', 'b' ) ) + ); + $router->add( '/$2/$1', + array( 'test2' => '$2' ), + array( '$2' => 'c' ) + ); + $router->add( '/$1' ); + + $matches = $router->parse( "/asdf/Foo" ); + $this->assertEquals( $matches, array( 'title' => "asdf/Foo" ) ); + + $matches = $router->parse( "/a/Foo" ); + $this->assertEquals( $matches, array( 'title' => "Foo", 'test' => 'a' ) ); + + $matches = $router->parse( "/c/Foo" ); + $this->assertEquals( $matches, array( 'title' => "Foo", 'test2' => 'c' ) ); + } + + public function callbackForTest( &$matches, $data ) { + $matches['x'] = $data['$1']; + $matches['foo'] = $data['foo']; + } + + public function testCallback() { + $router = new PathRouter; + $router->add( "/$1", + array( 'a' => 'b', 'data:foo' => 'bar' ), + array( 'callback' => array( $this, 'callbackForTest' ) ) + ); + $matches = $router->parse( '/Foo' ); + $this->assertEquals( $matches, array( + 'title' => "Foo", + 'x' => 'Foo', + 'a' => 'b', + 'foo' => 'bar' + ) ); + } + + /** + * Test to ensure that matches are not made if a parameter expects nonexistent input + */ + public function testFail() { + $router = new PathRouter; + $router->add( "/wiki/$1", array( 'title' => "$1$2" ) ); + $matches = $router->parse( "/wiki/A" ); + $this->assertEquals( array(), $matches ); + } + + /** + * Test to ensure weight of paths is handled correctly + */ + public function testWeight() { + $router = new PathRouter; + $router->addStrict( "/Bar", array( 'ping' => 'pong' ) ); + $router->add( "/asdf-$1", array( 'title' => 'qwerty-$1' ) ); + $router->add( "/$1" ); + $router->add( "/qwerty-$1", array( 'title' => 'asdf-$1' ) ); + $router->addStrict( "/Baz", array( 'marco' => 'polo' ) ); + $router->add( "/a/$1" ); + $router->add( "/asdf/$1" ); + $router->add( "/$2/$1", array( 'unrestricted' => '$2' ) ); + $router->add( array( 'qwerty' => "/qwerty/$1" ), array( 'qwerty' => '$key' ) ); + $router->add( "/$2/$1", array( 'restricted-to-y' => '$2' ), array( '$2' => 'y' ) ); + + foreach( array( + "/Foo" => array( 'title' => "Foo" ), + "/Bar" => array( 'ping' => 'pong' ), + "/Baz" => array( 'marco' => 'polo' ), + "/asdf-foo" => array( 'title' => "qwerty-foo" ), + "/qwerty-bar" => array( 'title' => "asdf-bar" ), + "/a/Foo" => array( 'title' => "Foo" ), + "/asdf/Foo" => array( 'title' => "Foo" ), + "/qwerty/Foo" => array( 'title' => "Foo", 'qwerty' => 'qwerty' ), + "/baz/Foo" => array( 'title' => "Foo", 'unrestricted' => 'baz' ), + "/y/Foo" => array( 'title' => "Foo", 'restricted-to-y' => 'y' ), + ) as $path => $result ) { + $this->assertEquals( $router->parse( $path ), $result ); + } + } + + /** + * Make sure the router handles titles like Special:Recentchanges correctly + */ + public function testSpecial() { + $matches = $this->basicRouter->parse( "/wiki/Special:Recentchanges" ); + $this->assertEquals( $matches, array( 'title' => "Special:Recentchanges" ) ); + } + + /** + * Make sure the router decodes urlencoding properly + */ + public function testUrlencoding() { + $matches = $this->basicRouter->parse( "/wiki/Title_With%20Space" ); + $this->assertEquals( $matches, array( 'title' => "Title_With Space" ) ); + } + + public function dataRegexpChars() { + return array( + array( "$" ), + array( "$1" ), + array( "\\" ), + array( "\\$1" ), + ); + } + + /** + * Make sure the router doesn't break on special characters like $ used in regexp replacements + * @dataProvider dataRegexpChars + */ + public function testRegexpChars( $char ) { + $matches = $this->basicRouter->parse( "/wiki/$char" ); + $this->assertEquals( $matches, array( 'title' => "$char" ) ); + } + + /** + * Make sure the router handles characters like +&() properly + */ + public function testCharacters() { + $matches = $this->basicRouter->parse( "/wiki/Plus+And&Dollar\\Stuff();[]{}*" ); + $this->assertEquals( $matches, array( 'title' => "Plus+And&Dollar\\Stuff();[]{}*" ) ); + } + + /** + * Make sure the router handles unicode characters correctly + * @depends testSpecial + * @depends testUrlencoding + * @depends testCharacters + */ + public function testUnicode() { + $matches = $this->basicRouter->parse( "/wiki/Spécial:Modifications_récentes" ); + $this->assertEquals( $matches, array( 'title' => "Spécial:Modifications_récentes" ) ); + + $matches = $this->basicRouter->parse( "/wiki/Sp%C3%A9cial:Modifications_r%C3%A9centes" ); + $this->assertEquals( $matches, array( 'title' => "Spécial:Modifications_récentes" ) ); + } + + /** + * Ensure the router doesn't choke on long paths. + */ + public function testLength() { + $matches = $this->basicRouter->parse( "/wiki/Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum." ); + $this->assertEquals( $matches, array( 'title' => "Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum." ) ); + } + + + /** + * Ensure that the php passed site of parameter values are not urldecoded + */ + public function testPatternUrlencoding() { + $router = new PathRouter; + $router->add( "/wiki/$1", array( 'title' => '%20:$1' ) ); + $matches = $router->parse( "/wiki/Foo" ); + $this->assertEquals( $matches, array( 'title' => '%20:Foo' ) ); + } + + /** + * Ensure that raw parameter values do not have any variable replacements or urldecoding + */ + public function testRawParamValue() { + $router = new PathRouter; + $router->add( "/wiki/$1", array( 'title' => array( 'value' => 'bar%20$1' ) ) ); + $matches = $router->parse( "/wiki/Foo" ); + $this->assertEquals( $matches, array( 'title' => 'bar%20$1' ) ); + } + +} diff --git a/tests/phpunit/includes/Providers.php b/tests/phpunit/includes/Providers.php index 02898673..f451f8a0 100644 --- a/tests/phpunit/includes/Providers.php +++ b/tests/phpunit/includes/Providers.php @@ -2,8 +2,8 @@ /** * Generic providers for the MediaWiki PHPUnit test suite * - * @author Ashar Voultoiz - * @copyright Copyright © 2011, Ashar Voultoiz + * @author Antoine Musso + * @copyright Copyright © 2011, Antoine Musso * @file */ diff --git a/tests/phpunit/includes/ResourceLoaderTest.php b/tests/phpunit/includes/ResourceLoaderTest.php index 30a69c5e..ab704839 100644 --- a/tests/phpunit/includes/ResourceLoaderTest.php +++ b/tests/phpunit/includes/ResourceLoaderTest.php @@ -1,6 +1,6 @@ <?php -class ResourceLoaderTest extends PHPUnit_Framework_TestCase { +class ResourceLoaderTest extends MediaWikiTestCase { protected static $resourceLoaderRegisterModulesHook; diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php index 40d6cf77..b76aa5c7 100644 --- a/tests/phpunit/includes/SanitizerTest.php +++ b/tests/phpunit/includes/SanitizerTest.php @@ -109,5 +109,48 @@ class SanitizerTest extends MediaWikiTestCase { $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&"' ), array( 'foo' => '&"' ), 'Special chars can be provided as entities' ); $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&foobar;' ), array( 'foo' => '&foobar;' ), 'Entity-like items are accepted' ); } + + function testDeprecatedAttributes() { + $GLOBALS['wgCleanupPresentationalAttributes'] = true; + $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), ' style="clear: left;"', 'Deprecated attributes are converted to styles when enabled.' ); + $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="all"', 'br' ), ' style="clear: both;"', 'clear=all is converted to clear: both; not clear: all;' ); + $this->assertEquals( Sanitizer::fixTagAttributes( 'CLEAR="ALL"', 'br' ), ' style="clear: both;"', 'clear=ALL is not treated differently from clear=all' ); + $this->assertEquals( Sanitizer::fixTagAttributes( 'width="100"', 'td' ), ' style="width: 100px;"', 'Numeric sizes use pixels instead of numbers.' ); + $this->assertEquals( Sanitizer::fixTagAttributes( 'width="100%"', 'td' ), ' style="width: 100%;"', 'Units are allowed in sizes.' ); + $this->assertEquals( Sanitizer::fixTagAttributes( 'WIDTH="100%"', 'td' ), ' style="width: 100%;"', 'Uppercase WIDTH is treated as lowercase width.' ); + $this->assertEquals( Sanitizer::fixTagAttributes( 'WiDTh="100%"', 'td' ), ' style="width: 100%;"', 'Mixed case does not break WiDTh.' ); + $this->assertEquals( Sanitizer::fixTagAttributes( 'nowrap="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute is output as white-space: nowrap; not something else.' ); + $this->assertEquals( Sanitizer::fixTagAttributes( 'nowrap=""', 'td' ), ' style="white-space: nowrap;"', 'nowrap="" is considered true, not false' ); + $this->assertEquals( Sanitizer::fixTagAttributes( 'NOWRAP="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute works when uppercase.' ); + $this->assertEquals( Sanitizer::fixTagAttributes( 'NoWrAp="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute works when mixed-case.' ); + $GLOBALS['wgCleanupPresentationalAttributes'] = false; + $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), ' clear="left"', 'Deprecated attributes are not converted to styles when enabled.' ); + } + + /** + * @dataProvider provideCssCommentsFixtures + */ + function testCssCommentsChecking( $expected, $css, $message = '' ) { + $this->assertEquals( + $expected, + Sanitizer::checkCss( $css ), + $message + ); + } + + function provideCssCommentsFixtures() { + /** array( <expected>, <css>, [message] ) */ + return array( + array( ' ', '/**/' ), + array( ' ', '/****/' ), + array( ' ', '/* comment */' ), + array( ' ', "\\2f\\2a foo \\2a\\2f", + 'Backslash-escaped comments must be stripped (bug 28450)' ), + array( '', '/* unfinished comment structure', + 'Remove anything after a comment-start token' ), + array( '', "\\2f\\2a unifinished comment'", + 'Remove anything after a backslash-escaped comment-start token' ), + ); + } } diff --git a/tests/phpunit/includes/UserIsValidEmailAddrTest.php b/tests/phpunit/includes/SanitizerValidateEmailTest.php index 99bf718e..14d799cf 100644 --- a/tests/phpunit/includes/UserIsValidEmailAddrTest.php +++ b/tests/phpunit/includes/SanitizerValidateEmailTest.php @@ -1,12 +1,12 @@ <?php -class UserIsValidEmailAddrTest extends MediaWikiTestCase { +class SanitizerValidateEmailTest extends MediaWikiTestCase { private function checkEmail( $addr, $expected = true, $msg = '') { if( $msg == '' ) { $msg = "Testing $addr"; } $this->assertEquals( $expected, - User::isValidEmailAddr( $addr ), + Sanitizer::validateEmail( $addr ), $msg ); } diff --git a/tests/phpunit/includes/SeleniumConfigurationTest.php b/tests/phpunit/includes/SeleniumConfigurationTest.php index 750524eb..8589c188 100644 --- a/tests/phpunit/includes/SeleniumConfigurationTest.php +++ b/tests/phpunit/includes/SeleniumConfigurationTest.php @@ -2,13 +2,13 @@ class SeleniumConfigurationTest extends MediaWikiTestCase { - /* + /** * The file where the test temporarity stores the selenium config. * This should be cleaned up as part of teardown. */ private $tempFileName; - /* + /** * String containing the a sample selenium settings */ private $testConfig0 = @@ -32,14 +32,14 @@ runAgainstGrid = false testSuite[SimpleSeleniumTestSuite] = "tests/selenium/SimpleSeleniumTestSuite.php" testSuite[TestSuiteName] = "testSuitePath" '; - /* + /** * Array of expected browsers from $testConfig0 */ private $testBrowsers0 = array( 'firefox' => '*firefox', 'iexplorer' => '*iexploreproxy', 'chrome' => '*chrome' ); - /* + /** * Array of expected selenium settings from $testConfig0 */ private $testSettings0 = array( @@ -55,7 +55,7 @@ testSuite[TestSuiteName] = "testSuitePath" 'jUnitLogFile' => null, 'runAgainstGrid' => null ); - /* + /** * Array of expected testSuites from $testConfig0 */ private $testSuites0 = array( @@ -64,7 +64,7 @@ testSuite[TestSuiteName] = "testSuitePath" ); - /* + /** * Another sample selenium settings file contents */ private $testConfig1 = @@ -73,11 +73,11 @@ testSuite[TestSuiteName] = "testSuitePath" host = "localhost" testBrowser = "firefox" '; - /* + /** * Expected browsers from $testConfig1 */ private $testBrowsers1 = null; - /* + /** * Expected selenium settings from $testConfig1 */ private $testSettings1 = array( @@ -93,7 +93,7 @@ testBrowser = "firefox" 'jUnitLogFile' => null, 'runAgainstGrid' => null ); - /* + /** * Expected test suites from $testConfig1 */ private $testSuites1 = null; @@ -105,7 +105,7 @@ testBrowser = "firefox" } } - /* + /** * Clean up the temporary file used to store the selenium settings. */ public function tearDown() { @@ -199,7 +199,7 @@ testBrowser = "firefox" } - /* + /** * create a temp file and write text to it. * @param $testToWrite the text to write to the temp file */ @@ -210,7 +210,7 @@ testBrowser = "firefox" fclose($tempFile); } - /* + /** * Returns an array containing: * The contents of the selenium cingiguration ini file * The expected selenium configuration array that getSeleniumSettings should return diff --git a/tests/phpunit/includes/TemplateCategoriesTest.php b/tests/phpunit/includes/TemplateCategoriesTest.php new file mode 100644 index 00000000..de9d6dc6 --- /dev/null +++ b/tests/phpunit/includes/TemplateCategoriesTest.php @@ -0,0 +1,38 @@ +<?php + +/** + * @group Database + */ +require dirname( __FILE__ ) . "/../../../maintenance/runJobs.php"; + +class TemplateCategoriesTest extends MediaWikiLangTestCase { + + function testTemplateCategories() { + global $wgUser; + + $title = Title::newFromText( "Categorized from template" ); + $article = new Article( $title ); + $wgUser = new User(); + $wgUser->mRights['*'] = array( 'createpage', 'edit', 'purge' ); + + $status = $article->doEdit( '{{Categorising template}}', 'Create a page with a template', 0 ); + $this->assertEquals( + array() + , $title->getParentCategories() + ); + + $template = new Article( Title::newFromText( 'Template:Categorising template' ) ); + $status = $template->doEdit( '[[Category:Solved bugs]]', 'Add a category through a template', 0 ); + + // Run the job queue + $jobs = new RunJobs; + $jobs->loadParamsAndArgs( null, array( 'quiet' => true ), null ); + $jobs->execute(); + + $this->assertEquals( + array( 'Category:Solved_bugs' => $title->getPrefixedText() ) + , $title->getParentCategories() + ); + } + +} diff --git a/tests/phpunit/includes/TitleMethodsTest.php b/tests/phpunit/includes/TitleMethodsTest.php new file mode 100644 index 00000000..2f1103e8 --- /dev/null +++ b/tests/phpunit/includes/TitleMethodsTest.php @@ -0,0 +1,78 @@ +<?php + +class TitleMethodsTest extends MediaWikiTestCase { + + public function dataEquals() { + return array( + array( 'Main Page', 'Main Page', true ), + array( 'Main Page', 'Not The Main Page', false ), + array( 'Main Page', 'Project:Main Page', false ), + array( 'File:Example.png', 'Image:Example.png', true ), + array( 'Special:Version', 'Special:Version', true ), + array( 'Special:Version', 'Special:Recentchanges', false ), + array( 'Special:Version', 'Main Page', false ), + ); + } + + /** + * @dataProvider dataEquals + */ + public function testEquals( $titleA, $titleB, $expectedBool ) { + $titleA = Title::newFromText( $titleA ); + $titleB = Title::newFromText( $titleB ); + + $this->assertEquals( $titleA->equals( $titleB ), $expectedBool ); + $this->assertEquals( $titleB->equals( $titleA ), $expectedBool ); + } + + public function dataInNamespace() { + return array( + array( 'Main Page', NS_MAIN, true ), + array( 'Main Page', NS_TALK, false ), + array( 'Main Page', NS_USER, false ), + array( 'User:Foo', NS_USER, true ), + array( 'User:Foo', NS_USER_TALK, false ), + array( 'User:Foo', NS_TEMPLATE, false ), + array( 'User_talk:Foo', NS_USER_TALK, true ), + array( 'User_talk:Foo', NS_USER, false ), + ); + } + + /** + * @dataProvider dataInNamespace + */ + public function testInNamespace( $title, $ns, $expectedBool ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $title->inNamespace( $ns ), $expectedBool ); + } + + public function testInNamespaces() { + $mainpage = Title::newFromText( 'Main Page' ); + $this->assertTrue( $mainpage->inNamespaces( NS_MAIN, NS_USER ) ); + $this->assertTrue( $mainpage->inNamespaces( array( NS_MAIN, NS_USER ) ) ); + $this->assertTrue( $mainpage->inNamespaces( array( NS_USER, NS_MAIN ) ) ); + $this->assertFalse( $mainpage->inNamespaces( array( NS_PROJECT, NS_TEMPLATE ) ) ); + } + + public function dataHasSubjectNamespace() { + return array( + array( 'Main Page', NS_MAIN, true ), + array( 'Main Page', NS_TALK, true ), + array( 'Main Page', NS_USER, false ), + array( 'User:Foo', NS_USER, true ), + array( 'User:Foo', NS_USER_TALK, true ), + array( 'User:Foo', NS_TEMPLATE, false ), + array( 'User_talk:Foo', NS_USER_TALK, true ), + array( 'User_talk:Foo', NS_USER, true ), + ); + } + + /** + * @dataProvider dataHasSubjectNamespace + */ + public function testHasSubjectNamespace( $title, $ns, $expectedBool ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $title->hasSubjectNamespace( $ns ), $expectedBool ); + } + +} diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php index 1b179686..f62ac5dd 100644 --- a/tests/phpunit/includes/TitlePermissionTest.php +++ b/tests/phpunit/includes/TitlePermissionTest.php @@ -5,12 +5,16 @@ */ class TitlePermissionTest extends MediaWikiLangTestCase { protected $title; - protected $user; - protected $anonUser; - protected $userUser; - protected $altUser; - protected $userName; - protected $altUserName; + + /** + * @var User + */ + protected $user, $anonUser, $userUser, $altUser; + + /** + * @var string + */ + protected $userName, $altUserName; function setUp() { global $wgLocaltimezone, $wgLocalTZoffset, $wgMemc, $wgContLang, $wgLang; @@ -56,6 +60,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase { } function setUserPerm( $perm ) { + // Setting member variables is evil!!! + if ( is_array( $perm ) ) { $this->user->mRights = $perm; } else { @@ -299,7 +305,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->assertEquals( $check[$action][3], $this->title->userCan( $action, true ) ); $this->assertEquals( $check[$action][3], - $this->title->quickUserCan( $action, false ) ); + $this->title->quickUserCan( $action ) ); # count( User::getGroupsWithPermissions( $action ) ) < 1 } @@ -451,7 +457,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->user ) ); $this->assertEquals( true, - $this->title->quickUserCan( 'edit', false ) ); + $this->title->quickUserCan( 'edit' ) ); $this->title->mRestrictions = array( "edit" => array( 'bogus', "sysop", "protect", "" ), "bogus" => array( 'bogus', "sysop", "protect", "" ) ); @@ -491,9 +497,9 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->user ) ); $this->title->mCascadeRestriction = true; $this->assertEquals( false, - $this->title->quickUserCan( 'bogus', false ) ); + $this->title->quickUserCan( 'bogus' ) ); $this->assertEquals( false, - $this->title->quickUserCan( 'edit', false ) ); + $this->title->quickUserCan( 'edit' ) ); $this->assertEquals( array( array( 'badaccess-group0' ), array( 'protectedpagetext', 'bogus' ), array( 'protectedpagetext', 'protect' ), @@ -537,7 +543,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setTitle( NS_MAIN, "test page" ); $this->title->mTitleProtection['pt_create_perm'] = ''; $this->title->mTitleProtection['pt_user'] = $this->user->getID(); - $this->title->mTitleProtection['pt_expiry'] = Block::infinity(); + $this->title->mTitleProtection['pt_expiry'] = wfGetDB( DB_SLAVE )->getInfinity(); $this->title->mTitleProtection['pt_reason'] = 'test'; $this->title->mCascadeRestriction = false; @@ -574,7 +580,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->title->userCan( 'move' ) ); $this->title->mInterwiki = "no"; - $this->assertEquals( array( array( 'immobile-page' ) ), + $this->assertEquals( array( array( 'immobile-source-page' ) ), $this->title->getUserPermissionsErrors( 'move', $this->user ) ); $this->assertEquals( false, $this->title->userCan( 'move' ) ); @@ -623,7 +629,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $prev = time(); $now = time() + 120; $this->user->mBlockedby = $this->user->getId(); - $this->user->mBlock = new Block( '127.0.8.1', $this->user->getId(), $this->user->getId(), + $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(), 'no reason given', $prev + 3600, 1, 0 ); $this->user->mBlock->mTimestamp = 0; $this->assertEquals( array( array( 'autoblockedtext', @@ -640,7 +646,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { global $wgLocalTZoffset; $wgLocalTZoffset = -60; $this->user->mBlockedby = $this->user->getName(); - $this->user->mBlock = new Block( '127.0.8.1', 2, 1, 'no reason given', $now, 0, 10 ); + $this->user->mBlock = new Block( '127.0.8.1', 0, 1, 'no reason given', $now, 0, 10 ); $this->assertEquals( array( array( 'blockedtext', '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1', diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index e7bb98ac..1c8be5f9 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -41,10 +41,10 @@ class TitleTest extends MediaWikiTestCase { /** * Auth-less test of Title::isValidMoveOperation * + * @group Database * @param string $source * @param string $target - * @param array|string|true $requiredErrors - * @group Database + * @param array|string|true $expected Required error * @dataProvider dataTestIsValidMoveOperation */ function testIsValidMoveOperation( $source, $target, $expected ) { diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php index df91aca8..ef03e835 100644 --- a/tests/phpunit/includes/UserTest.php +++ b/tests/phpunit/includes/UserTest.php @@ -1,47 +1,67 @@ <?php +define( 'NS_UNITTEST', 5600 ); +define( 'NS_UNITTEST_TALK', 5601 ); + +/** + * @group Database + */ class UserTest extends MediaWikiTestCase { protected $savedGroupPermissions, $savedRevokedPermissions; - + + /** + * @var User + */ + protected $user; + public function setUp() { parent::setUp(); - + $this->savedGroupPermissions = $GLOBALS['wgGroupPermissions']; $this->savedRevokedPermissions = $GLOBALS['wgRevokePermissions']; - + $this->setUpPermissionGlobals(); + $this->setUpUser(); } private function setUpPermissionGlobals() { global $wgGroupPermissions, $wgRevokePermissions; - + + # Data for regular $wgGroupPermissions test $wgGroupPermissions['unittesters'] = array( + 'test' => true, 'runtest' => true, 'writetest' => false, 'nukeworld' => false, ); $wgGroupPermissions['testwriters'] = array( + 'test' => true, 'writetest' => true, 'modifytest' => true, ); - + # Data for regular $wgRevokePermissions test $wgRevokePermissions['formertesters'] = array( 'runtest' => true, ); } + private function setUpUser() { + $this->user = new User; + $this->user->addGroup( 'unittesters' ); + } + public function tearDown() { parent::tearDown(); - + $GLOBALS['wgGroupPermissions'] = $this->savedGroupPermissions; $GLOBALS['wgRevokePermissions'] = $this->savedRevokedPermissions; } - + public function testGroupPermissions() { $rights = User::getGroupPermissions( array( 'unittesters' ) ); $this->assertContains( 'runtest', $rights ); $this->assertNotContains( 'writetest', $rights ); $this->assertNotContains( 'modifytest', $rights ); $this->assertNotContains( 'nukeworld', $rights ); - + $rights = User::getGroupPermissions( array( 'unittesters', 'testwriters' ) ); $this->assertContains( 'runtest', $rights ); $this->assertContains( 'writetest', $rights ); @@ -53,6 +73,71 @@ class UserTest extends MediaWikiTestCase { $this->assertNotContains( 'runtest', $rights ); $this->assertNotContains( 'writetest', $rights ); $this->assertNotContains( 'modifytest', $rights ); - $this->assertNotContains( 'nukeworld', $rights ); + $this->assertNotContains( 'nukeworld', $rights ); + } + + public function testUserPermissions() { + $rights = $this->user->getRights(); + $this->assertContains( 'runtest', $rights ); + $this->assertNotContains( 'writetest', $rights ); + $this->assertNotContains( 'modifytest', $rights ); + $this->assertNotContains( 'nukeworld', $rights ); + } + + /** + * @dataProvider provideGetGroupsWithPermission + */ + public function testGetGroupsWithPermission( $expected, $right ) { + $result = User::getGroupsWithPermission( $right ); + sort( $result ); + sort( $expected ); + + $this->assertEquals( $expected, $result, "Groups with permission $right" ); + } + + public function provideGetGroupsWithPermission() { + return array( + array( + array( 'unittesters', 'testwriters' ), + 'test' + ), + array( + array( 'unittesters' ), + 'runtest' + ), + array( + array( 'testwriters' ), + 'writetest' + ), + array( + array( 'testwriters' ), + 'modifytest' + ), + ); + } + + /** + * @dataProvider provideUserNames + */ + public function testIsValidUserName( $username, $result, $message ) { + $this->assertEquals( $this->user->isValidUserName( $username ), $result, $message ); + } + + public function provideUserNames() { + return array( + array( '', false, 'Empty string' ), + array( ' ', false, 'Blank space' ), + array( 'abcd', false, 'Starts with small letter' ), + array( 'Ab/cd', false, 'Contains slash' ), + array( 'Ab cd' , true, 'Whitespace' ), + array( '192.168.1.1', false, 'IP' ), + array( 'User:Abcd', false, 'Reserved Namespace' ), + array( '12abcd232' , true , 'Starts with Numbers' ), + array( '?abcd' , true, 'Start with ? mark' ), + array( '#abcd', false, 'Start with #' ), + array( 'Abcdകഖഗഘ', true, ' Mixed scripts' ), + array( 'ജോസ്തോമസ്', false, 'ZWNJ- Format control character' ), + array( 'Ab cd', false, ' Ideographic space' ), + ); } -}
\ No newline at end of file +} diff --git a/tests/phpunit/includes/WebRequestTest.php b/tests/phpunit/includes/WebRequestTest.php index 1cfbd3fc..e72408f6 100644 --- a/tests/phpunit/includes/WebRequestTest.php +++ b/tests/phpunit/includes/WebRequestTest.php @@ -85,4 +85,101 @@ class WebRequestTest extends MediaWikiTestCase { ), ); } + + /** + * @dataProvider provideGetIP + */ + function testGetIP( $expected, $input, $squid, $private, $description ) { + global $wgSquidServersNoPurge, $wgUsePrivateIPs; + $oldServer = $_SERVER; + $_SERVER = $input; + $wgSquidServersNoPurge = $squid; + $wgUsePrivateIPs = $private; + $request = new WebRequest(); + $result = $request->getIP(); + $_SERVER = $oldServer; + $this->assertEquals( $expected, $result, $description ); + } + + function provideGetIP() { + return array( + array( + '127.0.0.1', + array( + 'REMOTE_ADDR' => '127.0.0.1' + ), + array(), + false, + 'Simple IPv4' + ), + array( + '::1', + array( + 'REMOTE_ADDR' => '::1' + ), + array(), + false, + 'Simple IPv6' + ), + array( + '12.0.0.3', + array( + 'REMOTE_ADDR' => '12.0.0.1', + 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' + ), + array( '12.0.0.1', '12.0.0.2' ), + false, + 'With X-Forwaded-For' + ), + array( + '12.0.0.1', + array( + 'REMOTE_ADDR' => '12.0.0.1', + 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' + ), + array(), + false, + 'With X-Forwaded-For and disallowed server' + ), + array( + '12.0.0.2', + array( + 'REMOTE_ADDR' => '12.0.0.1', + 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' + ), + array( '12.0.0.1' ), + false, + 'With multiple X-Forwaded-For and only one allowed server' + ), + array( + '12.0.0.2', + array( + 'REMOTE_ADDR' => '12.0.0.2', + 'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2' + ), + array( '12.0.0.1', '12.0.0.2' ), + false, + 'With X-Forwaded-For and private IP' + ), + array( + '10.0.0.3', + array( + 'REMOTE_ADDR' => '12.0.0.2', + 'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2' + ), + array( '12.0.0.1', '12.0.0.2' ), + true, + 'With X-Forwaded-For and private IP (allowed)' + ), + ); + } + + /** + * @expectedException MWException + */ + function testGetIpLackOfRemoteAddrThrowAnException() { + $request = new WebRequest(); + # Next call throw an exception about lacking an IP + $request->getIP(); + } } diff --git a/tests/phpunit/includes/XmlSelectTest.php b/tests/phpunit/includes/XmlSelectTest.php index bf761e3d..2407c151 100644 --- a/tests/phpunit/includes/XmlSelectTest.php +++ b/tests/phpunit/includes/XmlSelectTest.php @@ -80,7 +80,7 @@ class XmlSelectTest extends MediaWikiTestCase { $this->select->addOption( 'foo2' ); $this->assertEquals( '<select><option value="foo1">foo1</option>' . "\n" . -'<option value="bar1" selected="selected">bar1</option>' . "\n" . +'<option value="bar1" selected="">bar1</option>' . "\n" . '<option value="foo2">foo2</option></select>', $this->select->getHTML() ); } @@ -96,7 +96,7 @@ class XmlSelectTest extends MediaWikiTestCase { $this->select->setDefault( 'bar1' ); # setting default after adding options $this->assertEquals( '<select><option value="foo1">foo1</option>' . "\n" . -'<option value="bar1" selected="selected">bar1</option>' . "\n" . +'<option value="bar1" selected="">bar1</option>' . "\n" . '<option value="foo2">foo2</option></select>', $this->select->getHTML() ); } diff --git a/tests/phpunit/includes/XmlTest.php b/tests/phpunit/includes/XmlTest.php index a6058ef6..1d9361f2 100644 --- a/tests/phpunit/includes/XmlTest.php +++ b/tests/phpunit/includes/XmlTest.php @@ -2,19 +2,43 @@ class XmlTest extends MediaWikiTestCase { private static $oldLang; + private static $oldNamespaces; public function setUp() { - global $wgLang, $wgLanguageCode; - + global $wgLang, $wgContLang; + self::$oldLang = $wgLang; - $wgLanguageCode = 'en'; - $wgLang = Language::factory( $wgLanguageCode ); + $wgLang = Language::factory( 'en' ); + + // Hardcode namespaces during test runs, + // so that html output based on existing namespaces + // can be properly evaluated. + self::$oldNamespaces = $wgContLang->getNamespaces(); + $wgContLang->setNamespaces( array( + -2 => 'Media', + -1 => 'Special', + 0 => '', + 1 => 'Talk', + 2 => 'User', + 3 => 'User_talk', + 4 => 'MyWiki', + 5 => 'MyWiki_Talk', + 6 => 'File', + 7 => 'File_talk', + 8 => 'MediaWiki', + 9 => 'MediaWiki_talk', + 10 => 'Template', + 11 => 'Template_talk', + 100 => 'Custom', + 101 => 'Custom_talk', + ) ); } - + public function tearDown() { - global $wgLang, $wgLanguageCode; + global $wgLang, $wgContLang; $wgLang = self::$oldLang; - $wgLanguageCode = $wgLang->getCode(); + + $wgContLang->setNamespaces( self::$oldNamespaces ); } public function testExpandAttributes() { @@ -88,6 +112,9 @@ class XmlTest extends MediaWikiTestCase { $this->assertEquals( '</element>', Xml::closeElement( 'element' ), 'closeElement() shortcut' ); } + /** + * @group Broken + */ public function testDateMenu( ) { $curYear = intval(gmdate('Y')); $prevYear = $curYear - 1; @@ -98,11 +125,10 @@ class XmlTest extends MediaWikiTestCase { $nextMonth = $curMonth + 1; if( $nextMonth == 13 ) { $nextMonth = 1; } - $this->assertEquals( '<label for="year">From year (and earlier):</label> <input name="year" size="4" value="2011" id="year" maxlength="4" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . '<option value="1">January</option>' . "\n" . -'<option value="2" selected="selected">February</option>' . "\n" . +'<option value="2" selected="">February</option>' . "\n" . '<option value="3">March</option>' . "\n" . '<option value="4">April</option>' . "\n" . '<option value="5">May</option>' . "\n" . @@ -139,7 +165,6 @@ class XmlTest extends MediaWikiTestCase { "Date menu year is the current one when not specified" ); - $this->markTestIncomplete( "Broken" ); // @todo FIXME: next month can be in the next year // test failing because it is now december $this->assertEquals( @@ -163,11 +188,57 @@ class XmlTest extends MediaWikiTestCase { '<option value="10">October</option>' . "\n" . '<option value="11">November</option>' . "\n" . '<option value="12">December</option></select>', - Xml::dateMenu( '', ''), + Xml::dateMenu( '', '' ), "Date menu with neither year or month" ); } + function testNamespaceSelector() { + $this->assertEquals( + '<select class="namespaceselector" id="namespace" name="namespace">' . "\n" . +'<option value="0">(Main)</option>' . "\n" . +'<option value="1">Talk</option>' . "\n" . +'<option value="2">User</option>' . "\n" . +'<option value="3">User talk</option>' . "\n" . +'<option value="4">MyWiki</option>' . "\n" . +'<option value="5">MyWiki Talk</option>' . "\n" . +'<option value="6">File</option>' . "\n" . +'<option value="7">File talk</option>' . "\n" . +'<option value="8">MediaWiki</option>' . "\n" . +'<option value="9">MediaWiki talk</option>' . "\n" . +'<option value="10">Template</option>' . "\n" . +'<option value="11">Template talk</option>' . "\n" . +'<option value="100">Custom</option>' . "\n" . +'<option value="101">Custom talk</option>' . "\n" . +'</select>', + Xml::namespaceSelector(), + 'Basic namespace selector without custom options' + ); + $this->assertEquals( + '<label for="namespace">Select a namespace:</label>' . +' <select class="namespaceselector" id="namespace" name="myname">' . "\n" . +'<option value="all">all</option>' . "\n" . +'<option value="0">(Main)</option>' . "\n" . +'<option value="1">Talk</option>' . "\n" . +'<option value="2" selected="">User</option>' . "\n" . +'<option value="3">User talk</option>' . "\n" . +'<option value="4">MyWiki</option>' . "\n" . +'<option value="5">MyWiki Talk</option>' . "\n" . +'<option value="6">File</option>' . "\n" . +'<option value="7">File talk</option>' . "\n" . +'<option value="8">MediaWiki</option>' . "\n" . +'<option value="9">MediaWiki talk</option>' . "\n" . +'<option value="10">Template</option>' . "\n" . +'<option value="11">Template talk</option>' . "\n" . +'<option value="100">Custom</option>' . "\n" . +'<option value="101">Custom talk</option>' . "\n" . +'</select>', + Xml::namespaceSelector( $selected = '2', $all = 'all', $element_name = 'myname', $label = 'Select a namespace:' ), + 'Basic namespace selector with custom values' + ); + } + + # # textarea # @@ -255,12 +326,12 @@ class XmlTest extends MediaWikiTestCase { function testEncodeJsVarArray() { $this->assertEquals( - '["a", 1]', + '["a",1]', Xml::encodeJsVar( array( 'a', 1 ) ), 'encodeJsVar() with array' ); $this->assertEquals( - '{"a": "a", "b": 1}', + '{"a":"a","b":1}', Xml::encodeJsVar( array( 'a' => 'a', 'b' => 1 ) ), 'encodeJsVar() with associative array' ); @@ -268,7 +339,7 @@ class XmlTest extends MediaWikiTestCase { function testEncodeJsVarObject() { $this->assertEquals( - '{"a": "a", "b": 1}', + '{"a":"a","b":1}', Xml::encodeJsVar( (object)array( 'a' => 'a', 'b' => 1 ) ), 'encodeJsVar() with object' ); diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php index 227555eb..b95d8214 100644 --- a/tests/phpunit/includes/api/ApiBlockTest.php +++ b/tests/phpunit/includes/api/ApiBlockTest.php @@ -15,24 +15,34 @@ class ApiBlockTest extends ApiTestCase { } function addDBData() { - $user = User::newFromName( 'UTBlockee' ); + $user = User::newFromName( 'UTApiBlockee' ); if ( $user->getId() == 0 ) { $user->addToDatabase(); - $user->setPassword( 'UTBlockeePassword' ); + $user->setPassword( 'UTApiBlockeePassword' ); $user->saveSettings(); } } + /** + * This test has probably always been broken and use an invalid token + * Bug tracking brokenness is https://bugzilla.wikimedia.org/35646 + * + * Root cause is https://gerrit.wikimedia.org/r/3434 + * Which made the Block/Unblock API to actually verify the token + * previously always considered valid (bug 34212). + * + * @group Broken + */ function testMakeNormalBlock() { $data = $this->getTokens(); - $user = User::newFromName( 'UTBlockee' ); + $user = User::newFromName( 'UTApiBlockee' ); if ( !$user->getId() ) { - $this->markTestIncomplete( "The user UTBlockee does not exist" ); + $this->markTestIncomplete( "The user UTApiBlockee does not exist" ); } if( !isset( $data[0]['query']['pages'] ) ) { @@ -45,15 +55,15 @@ class ApiBlockTest extends ApiTestCase { $data = $this->doApiRequest( array( 'action' => 'block', - 'user' => 'UTBlockee', - 'reason' => BlockTest::REASON, - 'token' => $pageinfo['blocktoken'] ), $data ); + 'user' => 'UTApiBlockee', + 'reason' => 'Some reason', + 'token' => $pageinfo['blocktoken'] ), $data, false, self::$users['sysop']->user ); - $block = Block::newFromTarget('UTBlockee'); + $block = Block::newFromTarget('UTApiBlockee'); $this->assertTrue( !is_null( $block ), 'Block is valid' ); - $this->assertEquals( 'UTBlockee', (string)$block->getTarget() ); + $this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() ); $this->assertEquals( 'Some reason', $block->mReason ); $this->assertEquals( 'infinity', $block->mExpiry ); diff --git a/tests/phpunit/includes/api/ApiPurgeTest.php b/tests/phpunit/includes/api/ApiPurgeTest.php index db1563e9..70c20746 100644 --- a/tests/phpunit/includes/api/ApiPurgeTest.php +++ b/tests/phpunit/includes/api/ApiPurgeTest.php @@ -9,33 +9,31 @@ class ApiPurgeTest extends ApiTestCase { parent::setUp(); $this->doLogin(); } - + + /** + * @group Broken + */ function testPurgeMainPage() { - if ( !Title::newFromText( 'UTPage' )->exists() ) { $this->markTestIncomplete( "The article [[UTPage]] does not exist" ); } - + $somePage = mt_rand(); $data = $this->doApiRequest( array( 'action' => 'purge', 'titles' => 'UTPage|' . $somePage . '|%5D' ) ); - - $this->assertArrayHasKey( 'purge', $data[0] ); - - $this->assertArrayHasKey( 0, $data[0]['purge'] ); - $this->assertArrayHasKey( 'purged', $data[0]['purge'][0] ); - $this->assertEquals( 'UTPage', $data[0]['purge'][0]['title'] ); - - $this->assertArrayHasKey( 1, $data[0]['purge'] ); - $this->assertArrayHasKey( 'missing', $data[0]['purge'][1] ); - $this->assertEquals( $somePage, $data[0]['purge'][1]['title'] ); - - $this->assertArrayHasKey( 2, $data[0]['purge'] ); - $this->assertArrayHasKey( 'invalid', $data[0]['purge'][2] ); - $this->assertEquals( '%5D', $data[0]['purge'][2]['title'] ); - + + $this->assertArrayHasKey( 'purge', $data[0], + "Must receive a 'purge' result from API" ); + + $this->assertEquals( 3, count( $data[0]['purge'] ), + "Purge request for three articles should give back three results received: " . var_export( $data[0]['purge'], true ) ); + + $pages = array( 'UTPage' => 'purged', $somePage => 'missing', '%5D' => 'invalid' ); + foreach( $data[0]['purge'] as $v ) { + $this->assertArrayHasKey( $pages[$v['title']], $v ); + } } } diff --git a/tests/phpunit/includes/api/ApiQueryTest.php b/tests/phpunit/includes/api/ApiQueryTest.php index 114eadf3..ae05a30a 100644 --- a/tests/phpunit/includes/api/ApiQueryTest.php +++ b/tests/phpunit/includes/api/ApiQueryTest.php @@ -22,10 +22,13 @@ class ApiQueryTest extends ApiTestCase { $this->assertArrayHasKey( 'query', $data[0] ); $this->assertArrayHasKey( 'normalized', $data[0]['query'] ); + // Forge a normalized title + $to = Title::newFromText( $wgMetaNamespace.':ArticleA' ); + $this->assertEquals( array( 'from' => 'Project:articleA', - 'to' => $wgMetaNamespace . ':ArticleA' + 'to' => $to->getPrefixedText(), ), $data[0]['query']['normalized'][0] ); @@ -50,7 +53,6 @@ class ApiQueryTest extends ApiTestCase { 'action' => 'query', 'titles' => $title . '|Talk:' ) ); - $this->assertArrayHasKey( 'query', $data[0] ); $this->assertArrayHasKey( 'pages', $data[0]['query'] ); $this->assertEquals( 2, count( $data[0]['query']['pages'] ) ); @@ -60,8 +62,6 @@ class ApiQueryTest extends ApiTestCase { $this->assertArrayHasKey( 'missing', $data[0]['query']['pages'][-2] ); $this->assertArrayHasKey( 'invalid', $data[0]['query']['pages'][-1] ); - - } } diff --git a/tests/phpunit/includes/api/ApiTest.php b/tests/phpunit/includes/api/ApiTest.php index a587e6b1..1d9c3238 100644 --- a/tests/phpunit/includes/api/ApiTest.php +++ b/tests/phpunit/includes/api/ApiTest.php @@ -96,7 +96,7 @@ class ApiTest extends ApiTestCase { "lgtoken" => $token, "lgname" => $user->username, "lgpassword" => "badnowayinhell", - ) + ), $ret[2] ); $result = $ret[0]; @@ -137,7 +137,7 @@ class ApiTest extends ApiTestCase { "lgtoken" => $token, "lgname" => $user->username, "lgpassword" => $user->password, - ) + ), $ret[2] ); $result = $ret[0]; @@ -148,6 +148,9 @@ class ApiTest extends ApiTestCase { $this->assertEquals( "Success", $a ); } + /** + * @group Broken + */ function testApiGotCookie() { $this->markTestIncomplete( "The server can't do external HTTP requests, and the internal one won't give cookies" ); @@ -192,24 +195,23 @@ class ApiTest extends ApiTestCase { } /** - * @depends testApiGotCookie + * @todo Finish filling me out...what are we trying to test here? */ - function testApiListPages( CookieJar $cj ) { - $this->markTestIncomplete( "Not done with this yet" ); + function testApiListPages() { global $wgServer; - - if ( $wgServer == "http://localhost" ) { + if ( !isset( $wgServer ) ) { $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' ); } - $req = MWHttpRequest::factory( self::$apiUrl . "?action=query&format=xml&prop=revisions&" . - "titles=Main%20Page&rvprop=timestamp|user|comment|content" ); - $req->setCookieJar( $cj ); - $req->execute(); - libxml_use_internal_errors( true ); - $sxe = simplexml_load_string( $req->getContent() ); - $this->assertNotInternalType( "bool", $sxe ); - $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) ); - $a = $sxe->query[0]->pages[0]->page[0]->attributes(); + + $ret = $this->doApiRequest( array( + 'action' => 'query', + 'prop' => 'revisions', + 'titles' => 'Main Page', + 'rvprop' => 'timestamp|user|comment|content', + ) ); + + $result = $ret[0]['query']['pages']; + $this->markTestIncomplete( "Somebody needs to finish loving me" ); } function testRunLogin() { @@ -228,7 +230,7 @@ class ApiTest extends ApiTestCase { 'action' => 'login', "lgtoken" => $token, "lgname" => $sysopUser->username, - "lgpassword" => $sysopUser->password ), $data ); + "lgpassword" => $sysopUser->password ), $data[2] ); $this->assertArrayHasKey( "login", $data[0] ); $this->assertArrayHasKey( "result", $data[0]['login'] ); diff --git a/tests/phpunit/includes/api/ApiTestCase.php b/tests/phpunit/includes/api/ApiTestCase.php index 2917c880..8801391f 100644 --- a/tests/phpunit/includes/api/ApiTestCase.php +++ b/tests/phpunit/includes/api/ApiTestCase.php @@ -7,6 +7,11 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { public static $users; protected static $apiUrl; + /** + * @var ApiTestContext + */ + protected $apiContext; + function setUp() { global $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser, $wgServer; @@ -21,31 +26,37 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { 'sysop' => new ApiTestUser( 'Apitestsysop', 'Api Test Sysop', - 'api_test_sysop@sample.com', + 'api_test_sysop@example.com', array( 'sysop' ) ), 'uploader' => new ApiTestUser( 'Apitestuser', 'Api Test User', - 'api_test_user@sample.com', + 'api_test_user@example.com', array() ) ); $wgUser = self::$users['sysop']->user; + $this->apiContext = new ApiTestContext(); + } - protected function doApiRequest( $params, $session = null, $appendModule = false ) { + protected function doApiRequest( $params, $session = null, $appendModule = false, $user = null ) { if ( is_null( $session ) ) { $session = array(); } - $request = new FauxRequest( $params, true, $session ); - $module = new ApiMain( $request, true ); + $context = $this->apiContext->newTestContext( $params, $session, $user ); + $module = new ApiMain( $context, true ); $module->execute(); - $results = array( $module->getResultData(), $request, $request->getSessionArray() ); + $results = array( + $module->getResultData(), + $context->getRequest(), + $context->getRequest()->getSessionArray() + ); if( $appendModule ) { $results[] = $module; } @@ -59,14 +70,15 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { * request, without actually requesting a "real" edit token * @param $params: key-value API params * @param $session: session array + * @param $user String|null A User object for the context */ - protected function doApiRequestWithToken( $params, $session ) { + protected function doApiRequestWithToken( $params, $session, $user = null ) { if ( $session['wsToken'] ) { // add edit token to fake session $session['wsEditToken'] = $session['wsToken']; // add token to request parameters $params['token'] = md5( $session['wsToken'] ) . User::EDIT_TOKEN_SUFFIX; - return $this->doApiRequest( $params, $session ); + return $this->doApiRequest( $params, $session, false, $user ); } else { throw new Exception( "request data not in right format" ); } @@ -91,12 +103,11 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { } protected function getTokenList( $user ) { - $GLOBALS['wgUser'] = $user->user; $data = $this->doApiRequest( array( 'action' => 'query', 'titles' => 'Main Page', 'intoken' => 'edit|delete|protect|move|block|unblock', - 'prop' => 'info' ) ); + 'prop' => 'info' ), false, $user->user ); return $data; } } @@ -137,3 +148,23 @@ class MockApi extends ApiBase { ); } } + +class ApiTestContext extends RequestContext { + + /** + * Returns a DerivativeContext with the request variables in place + * + * @param $params Array key-value API params + * @param $session Array session data + * @param $user User or null + * @return DerivativeContext + */ + public function newTestContext( $params, $session, $user = null ) { + $context = new DerivativeContext( $this ); + $context->setRequest( new FauxRequest( $params, true, $session ) ); + if ( $user !== null ) { + $context->setUser( $user ); + } + return $context; + } +} diff --git a/tests/phpunit/includes/api/ApiTestCaseUpload.php b/tests/phpunit/includes/api/ApiTestCaseUpload.php index e51e7214..39c79547 100644 --- a/tests/phpunit/includes/api/ApiTestCaseUpload.php +++ b/tests/phpunit/includes/api/ApiTestCaseUpload.php @@ -19,6 +19,10 @@ abstract class ApiTestCaseUpload extends ApiTestCase { $this->clearFakeUploads(); } + public function tearDown() { + $this->clearTempUpload(); + } + /** * Helper function -- remove files and associated articles by Title * @param $title Title: title to be removed @@ -33,8 +37,8 @@ abstract class ApiTestCaseUpload extends ApiTestCase { if ( !$status->isGood() ) { return false; } - $article = new Article( $title ); - $article->doDeleteArticle( "removing for test" ); + $page = WikiPage::factory( $title ); + $page->doDeleteArticle( "removing for test" ); // see if it now doesn't exist; reload $title = Title::newFromText( $title->getText(), NS_FILE ); @@ -56,7 +60,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase { * @param $filePath String: path to file on the filesystem */ public function deleteFileByContent( $filePath ) { - $hash = File::sha1Base36( $filePath ); + $hash = FSFile::getSha1Base36FromPath( $filePath ); $dupes = RepoGroup::singleton()->findBySha1( $hash ); $success = true; foreach ( $dupes as $dupe ) { @@ -100,6 +104,36 @@ abstract class ApiTestCaseUpload extends ApiTestCase { return true; } + function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ){ + $tmpName = tempnam( wfTempDir(), "" ); + // copy the chunk data to temp location: + if ( !file_put_contents( $tmpName, $chunkData ) ) { + throw new Exception( "couldn't copy chunk data to $tmpName" ); + } + + clearstatcache(); + $size = filesize( $tmpName ); + if ( $size === false ) { + throw new Exception( "couldn't stat $tmpName" ); + } + + $_FILES[ $fieldName ] = array( + 'name' => $fileName, + 'type' => $type, + 'tmp_name' => $tmpName, + 'size' => $size, + 'error' => null + ); + } + + function clearTempUpload() { + if( isset( $_FILES['file']['tmp_name'] ) ) { + $tmp = $_FILES['file']['tmp_name']; + if( file_exists( $tmp ) ) { + unlink( $tmp ); + } + } + } /** * Remove traces of previous fake uploads diff --git a/tests/phpunit/includes/api/ApiTestUser.php b/tests/phpunit/includes/api/ApiTestUser.php index df60682f..8d5f61a7 100644 --- a/tests/phpunit/includes/api/ApiTestUser.php +++ b/tests/phpunit/includes/api/ApiTestUser.php @@ -8,7 +8,7 @@ class ApiTestUser { public $groups; public $user; - function __construct( $username, $realname = 'Real Name', $email = 'sample@sample.com', $groups = array() ) { + function __construct( $username, $realname = 'Real Name', $email = 'sample@example.com', $groups = array() ) { $this->username = $username; $this->realname = $realname; $this->email = $email; diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php index 5c929784..7a700326 100644 --- a/tests/phpunit/includes/api/ApiUploadTest.php +++ b/tests/phpunit/includes/api/ApiUploadTest.php @@ -19,6 +19,9 @@ require_once( 'ApiTestCaseUpload.php' ); /** * @group Database + * @group Broken + * Broken test, reports false errors from time to time. + * See https://bugzilla.wikimedia.org/26169 * * This is pretty sucky... needs to be prettified. */ @@ -54,6 +57,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->assertEquals( "Success", $result['login']['result'] ); $this->assertArrayHasKey( 'lgtoken', $result['login'] ); + $this->assertNotEmpty( $session, 'API Login must return a session' ); return $session; } @@ -78,14 +82,11 @@ class ApiUploadTest extends ApiTestCaseUpload { * @depends testLogin */ public function testUploadMissingParams( $session ) { - global $wgUser; - $wgUser = self::$users['uploader']->user; - $exception = false; try { $this->doApiRequestWithToken( array( 'action' => 'upload', - ), $session ); + ), $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $exception = true; $this->assertEquals( "One of the parameters filekey, file, url, statuskey is required", @@ -99,20 +100,17 @@ class ApiUploadTest extends ApiTestCaseUpload { * @depends testLogin */ public function testUpload( $session ) { - global $wgUser; - $wgUser = self::$users['uploader']->user; - $extension = 'png'; $mimeType = 'image/png'; try { $randomImageGenerator = new RandomImageGenerator(); + $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } - $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); $filePath = $filePaths[0]; $fileSize = filesize( $filePath ); $fileName = basename( $filePath ); @@ -135,7 +133,8 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { - list( $result, , ) = $this->doApiRequestWithToken( $params, $session ); + list( $result, , ) = $this->doApiRequestWithToken( $params, $session, + self::$users['uploader']->user ); } catch ( UsageException $e ) { $exception = true; } @@ -155,9 +154,6 @@ class ApiUploadTest extends ApiTestCaseUpload { * @depends testLogin */ public function testUploadZeroLength( $session ) { - global $wgUser; - $wgUser = self::$users['uploader']->user; - $mimeType = 'image/png'; $filePath = tempnam( wfTempDir(), "" ); @@ -179,7 +175,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { - $this->doApiRequestWithToken( $params, $session ); + $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $this->assertContains( 'The file you submitted was empty', $e->getMessage() ); $exception = true; @@ -196,20 +192,17 @@ class ApiUploadTest extends ApiTestCaseUpload { * @depends testLogin */ public function testUploadSameFileName( $session ) { - global $wgUser; - $wgUser = self::$users['uploader']->user; - $extension = 'png'; $mimeType = 'image/png'; try { $randomImageGenerator = new RandomImageGenerator(); + $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } - $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() ); // we'll reuse this filename $fileName = basename( $filePaths[0] ); @@ -233,7 +226,8 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { - list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session ); + list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, + self::$users['uploader']->user ); } catch ( UsageException $e ) { $exception = true; } @@ -249,7 +243,8 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { - list( $result, , ) = $this->doApiRequestWithToken( $params, $session ); + list( $result, , ) = $this->doApiRequestWithToken( $params, $session, + self::$users['uploader']->user ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -270,19 +265,17 @@ class ApiUploadTest extends ApiTestCaseUpload { * @depends testLogin */ public function testUploadSameContent( $session ) { - global $wgUser; - $wgUser = self::$users['uploader']->user; - $extension = 'png'; $mimeType = 'image/png'; try { $randomImageGenerator = new RandomImageGenerator(); + $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } - $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); + $fileNames[0] = basename( $filePaths[0] ); $fileNames[1] = "SameContentAs" . $fileNames[0]; @@ -307,7 +300,8 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session ); + list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + self::$users['uploader']->user ); } catch ( UsageException $e ) { $exception = true; } @@ -332,7 +326,8 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session ); + list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + self::$users['uploader']->user ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -354,19 +349,19 @@ class ApiUploadTest extends ApiTestCaseUpload { */ public function testUploadStash( $session ) { global $wgUser; - $wgUser = self::$users['uploader']->user; + $wgUser = self::$users['uploader']->user; // @todo FIXME: still used somewhere $extension = 'png'; $mimeType = 'image/png'; try { $randomImageGenerator = new RandomImageGenerator(); + $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } - $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); $filePath = $filePaths[0]; $fileSize = filesize( $filePath ); $fileName = basename( $filePath ); @@ -389,7 +384,8 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session ); + list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + self::$users['uploader']->user ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -417,17 +413,156 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->clearFakeUploads(); $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session ); + list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + self::$users['uploader']->user ); } catch ( UsageException $e ) { $exception = true; } $this->assertTrue( isset( $result['upload'] ) ); $this->assertEquals( 'Success', $result['upload']['result'] ); - $this->assertFalse( $exception ); + $this->assertFalse( $exception, "No UsageException exception." ); // clean up $this->deleteFileByFilename( $fileName ); unlink( $filePath ); } -} + + + /** + * @depends testLogin + */ + public function testUploadChunks( $session ) { + global $wgUser; + $wgUser = self::$users['uploader']->user; // @todo FIXME: still used somewhere + + $chunkSize = 1048576; + // Download a large image file + // ( using RandomImageGenerator for large files is not stable ) + $mimeType = 'image/jpeg'; + $url = 'http://upload.wikimedia.org/wikipedia/commons/e/ed/Oberaargletscher_from_Oberaar%2C_2010_07.JPG'; + $filePath = wfTempDir() . '/Oberaargletscher_from_Oberaar.jpg'; + try { + // Only download if the file is not avaliable in the temp location: + if( !is_file( $filePath ) ){ + copy($url, $filePath); + } + } + catch ( Exception $e ) { + $this->markTestIncomplete( $e->getMessage() ); + } + $fileSize = filesize( $filePath ); + $fileName = basename( $filePath ); + + $this->deleteFileByFileName( $fileName ); + $this->deleteFileByContent( $filePath ); + + // Base upload params: + $params = array( + 'action' => 'upload', + 'stash' => 1, + 'filename' => $fileName, + 'filesize' => $fileSize, + 'offset' => 0, + ); + + // Upload chunks + $chunkSessionKey = false; + $resultOffset = 0; + // Open the file: + $handle = @fopen ($filePath, "r"); + if( $handle === false ){ + $this->markTestIncomplete( "could not open file: $filePath" ); + } + while (!feof ($handle)) { + // Get the current chunk + $chunkData = @fread( $handle, $chunkSize ); + + // Upload the current chunk into the $_FILE object: + $this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData ); + + // Check for chunkSessionKey + if( !$chunkSessionKey ){ + // Upload fist chunk ( and get the session key ) + try { + list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + self::$users['uploader']->user ); + } catch ( UsageException $e ) { + $this->markTestIncomplete( $e->getMessage() ); + } + // Make sure we got a valid chunk continue: + $this->assertTrue( isset( $result['upload'] ) ); + $this->assertTrue( isset( $result['upload']['filekey'] ) ); + // If we don't get a session key mark test incomplete. + if( ! isset( $result['upload']['filekey'] ) ){ + $this->markTestIncomplete( "no filekey provided" ); + } + $chunkSessionKey = $result['upload']['filekey']; + $this->assertEquals( 'Continue', $result['upload']['result'] ); + // First chunk should have chunkSize == offset + $this->assertEquals( $chunkSize, $result['upload']['offset'] ); + $resultOffset = $result['upload']['offset']; + continue; + } + // Filekey set to chunk session + $params['filekey'] = $chunkSessionKey; + // Update the offset ( always add chunkSize for subquent chunks should be in-sync with $result['upload']['offset'] ) + $params['offset'] += $chunkSize; + // Make sure param offset is insync with resultOffset: + $this->assertEquals( $resultOffset, $params['offset'] ); + // Upload current chunk + try { + list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + self::$users['uploader']->user ); + } catch ( UsageException $e ) { + $this->markTestIncomplete( $e->getMessage() ); + } + // Make sure we got a valid chunk continue: + $this->assertTrue( isset( $result['upload'] ) ); + $this->assertTrue( isset( $result['upload']['filekey'] ) ); + + // Check if we were on the last chunk: + if( $params['offset'] + $chunkSize >= $fileSize ){ + $this->assertEquals( 'Success', $result['upload']['result'] ); + break; + } else { + $this->assertEquals( 'Continue', $result['upload']['result'] ); + // update $resultOffset + $resultOffset = $result['upload']['offset']; + } + } + fclose ($handle); + + // Check that we got a valid file result: + wfDebug( __METHOD__ . " hohoh filesize {$fileSize} info {$result['upload']['imageinfo']['size']}\n\n"); + $this->assertEquals( $fileSize, $result['upload']['imageinfo']['size'] ); + $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] ); + $this->assertTrue( isset( $result['upload']['filekey'] ) ); + $filekey = $result['upload']['filekey']; + + // Now we should try to release the file from stash + $params = array( + 'action' => 'upload', + 'filekey' => $filekey, + 'filename' => $fileName, + 'comment' => 'dummy comment', + 'text' => "This is the page text for $fileName, altered", + ); + $this->clearFakeUploads(); + $exception = false; + try { + list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + self::$users['uploader']->user ); + } catch ( UsageException $e ) { + $exception = true; + } + $this->assertTrue( isset( $result['upload'] ) ); + $this->assertEquals( 'Success', $result['upload']['result'] ); + $this->assertFalse( $exception ); + + // clean up + $this->deleteFileByFilename( $fileName ); + // don't remove downloaded temporary file for fast subquent tests. + //unlink( $filePath ); + } +} diff --git a/tests/phpunit/includes/api/ApiWatchTest.php b/tests/phpunit/includes/api/ApiWatchTest.php index 3c7ff304..b7803746 100644 --- a/tests/phpunit/includes/api/ApiWatchTest.php +++ b/tests/phpunit/includes/api/ApiWatchTest.php @@ -41,6 +41,7 @@ class ApiWatchTest extends ApiTestCase { /** * @depends testWatchEdit + * @group Broken */ function testWatchClear() { @@ -92,7 +93,9 @@ class ApiWatchTest extends ApiTestCase { $this->assertArrayHasKey( 'edit', $data[0]['protect']['protections'][0] ); } - + /** + * @group Broken + */ function testGetRollbackToken() { $data = $this->getTokens(); diff --git a/tests/phpunit/includes/api/RandomImageGenerator.php b/tests/phpunit/includes/api/RandomImageGenerator.php index ae349978..86c0a828 100644 --- a/tests/phpunit/includes/api/RandomImageGenerator.php +++ b/tests/phpunit/includes/api/RandomImageGenerator.php @@ -1,6 +1,6 @@ <?php -/* +/** * RandomImageGenerator -- does what it says on the tin. * Requires Imagick, the ImageMagick library for PHP, or the command line equivalent (usually 'convert'). * @@ -27,12 +27,11 @@ class RandomImageGenerator { private $dictionaryFile; - private $minWidth = 400; - private $maxWidth = 800; - private $minHeight = 400; - private $maxHeight = 800; - private $shapesToDraw = 5; - private $imageWriteMethod; + private $minWidth = 400 ; + private $maxWidth = 800 ; + private $minHeight = 400 ; + private $maxHeight = 800 ; + private $shapesToDraw = 5 ; /** * Orientations: 0th row, 0th column, EXIF orientation code, rotation 2x2 matrix that is opposite of orientation @@ -41,35 +40,35 @@ class RandomImageGenerator { * (we also would need a non-symmetric shape for the images to test those, like a letter F) */ private static $orientations = array( - array( - '0thRow' => 'top', - '0thCol' => 'left', - 'exifCode' => 1, - 'counterRotation' => array( array( 1, 0 ), array( 0, 1 ) ) + array( + '0thRow' => 'top', + '0thCol' => 'left', + 'exifCode' => 1, + 'counterRotation' => array( array( 1, 0 ), array( 0, 1 ) ) ), - array( + array( '0thRow' => 'bottom', - '0thCol' => 'right', - 'exifCode' => 3, - 'counterRotation' => array( array( -1, 0 ), array( 0, -1 ) ) + '0thCol' => 'right', + 'exifCode' => 3, + 'counterRotation' => array( array( -1, 0 ), array( 0, -1 ) ) ), - array( - '0thRow' => 'right', - '0thCol' => 'top', - 'exifCode' => 6, - 'counterRotation' => array( array( 0, 1 ), array( 1, 0 ) ) + array( + '0thRow' => 'right', + '0thCol' => 'top', + 'exifCode' => 6, + 'counterRotation' => array( array( 0, 1 ), array( 1, 0 ) ) ), - array( - '0thRow' => 'left', - '0thCol' => 'bottom', - 'exifCode' => 8, - 'counterRotation' => array( array( 0, -1 ), array( -1, 0 ) ) + array( + '0thRow' => 'left', + '0thCol' => 'bottom', + 'exifCode' => 8, + 'counterRotation' => array( array( 0, -1 ), array( -1, 0 ) ) ) ); public function __construct( $options = array() ) { - foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 'maxHeight', 'shapesToDraw' ) as $property ) { + foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight', 'shapesToDraw' ) as $property ) { if ( isset( $options[$property] ) ) { $this->$property = $options[$property]; } @@ -77,10 +76,10 @@ class RandomImageGenerator { // find the dictionary file, to generate random names if ( !isset( $this->dictionaryFile ) ) { - foreach ( array( - '/usr/share/dict/words', - '/usr/dict/words', - dirname( __FILE__ ) . '/words.txt' ) + foreach ( array( + '/usr/share/dict/words', + '/usr/dict/words', + dirname( __FILE__ ) . '/words.txt' ) as $dictionaryFile ) { if ( is_file( $dictionaryFile ) and is_readable( $dictionaryFile ) ) { $this->dictionaryFile = $dictionaryFile; @@ -91,14 +90,6 @@ class RandomImageGenerator { if ( !isset( $this->dictionaryFile ) ) { throw new Exception( "RandomImageGenerator: dictionary file not found or not specified properly" ); } - - if ( !class_exists( 'Imagick' ) ) { - throw new Exception( 'No Imagick extension' ); - } - global $wgExiv2Command; - if ( !$wgExiv2Command || !is_executable( $wgExiv2Command ) ) { - throw new Exception( 'exiv2 not executable or $wgExiv2Command not set' ); - } } /** @@ -125,15 +116,16 @@ class RandomImageGenerator { */ function getImageWriteMethod( $format ) { global $wgUseImageMagick, $wgImageMagickConvertCommand; - if ( $format === 'svg' ) { + if ( $format === 'svg' ) { return 'writeSvg'; } else { // figure out how to write images - if ( class_exists( 'Imagick' ) ) { + global $wgExiv2Command; + if ( class_exists( 'Imagick' ) && $wgExiv2Command && is_executable( $wgExiv2Command ) ) { return 'writeImageWithApi'; } elseif ( $wgUseImageMagick && $wgImageMagickConvertCommand && is_executable( $wgImageMagickConvertCommand ) ) { return 'writeImageWithCommandLine'; - } + } } throw new Exception( "RandomImageGenerator: could not find a suitable method to write images in '$format' format" ); } @@ -219,7 +211,7 @@ class RandomImageGenerator { */ static function shapePointsToString( $shape ) { $points = array(); - foreach ( $shape as $point ) { + foreach ( $shape as $point ) { $points[] = $point['x'] . ',' . $point['y']; } return join( " ", $points ); @@ -232,16 +224,16 @@ class RandomImageGenerator { * @param $format: file format to write (which is obviously always svg here) * @param $filename: filename to write to */ - public function writeSvg( $spec, $format, $filename ) { + public function writeSvg( $spec, $format, $filename ) { $svg = new SimpleXmlElement( '<svg/>' ); $svg->addAttribute( 'xmlns', 'http://www.w3.org/2000/svg' ); - $svg->addAttribute( 'version', '1.1' ); - $svg->addAttribute( 'width', $spec['width'] ); - $svg->addAttribute( 'height', $spec['height'] ); + $svg->addAttribute( 'version', '1.1' ); + $svg->addAttribute( 'width', $spec['width'] ); + $svg->addAttribute( 'height', $spec['height'] ); $g = $svg->addChild( 'g' ); foreach ( $spec['draws'] as $drawSpec ) { $shape = $g->addChild( 'polygon' ); - $shape->addAttribute( 'fill', $drawSpec['fill'] ); + $shape->addAttribute( 'fill', $drawSpec['fill'] ); $shape->addAttribute( 'points', self::shapePointsToString( $drawSpec['shape'] ) ); }; if ( ! $fh = fopen( $filename, 'w' ) ) { @@ -260,20 +252,20 @@ class RandomImageGenerator { * @param $filename: filename to write to */ public function writeImageWithApi( $spec, $format, $filename ) { - // this is a hack because I can't get setImageOrientation() to work. See below. + // this is a hack because I can't get setImageOrientation() to work. See below. global $wgExiv2Command; $image = new Imagick(); /** - * If the format is 'jpg', will also add a random orientation -- the image will be drawn rotated with triangle points + * If the format is 'jpg', will also add a random orientation -- the image will be drawn rotated with triangle points * facing in some direction (0, 90, 180 or 270 degrees) and a countering rotation should turn the triangle points upward again */ $orientation = self::$orientations[0]; // default is normal orientation if ( $format == 'jpg' ) { $orientation = self::$orientations[ array_rand( self::$orientations ) ]; - $spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] ); + $spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] ); } - + $image->newImage( $spec['width'], $spec['height'], new ImagickPixel( $spec['fill'] ) ); foreach ( $spec['draws'] as $drawSpec ) { @@ -296,7 +288,7 @@ class RandomImageGenerator { $cmd = wfEscapeShellArg( $wgExiv2Command ) . " -M " . wfEscapeShellArg( "set Exif.Image.Orientation " . $orientation['exifCode'] ) - . " " + . " " . wfEscapeShellArg( $filename ); $retval = 0; @@ -304,15 +296,13 @@ class RandomImageGenerator { if ( $retval !== 0 ) { print "Error with $cmd: $retval, $err\n"; } - } - - + } } /** * Given an image specification, produce rotated version * This is used when simulating a rotated image capture with EXIF orientation - * @param $spec Object returned by getImageSpec + * @param $spec Object returned by getImageSpec * @param $matrix 2x2 transformation matrix * @return transformed Spec */ @@ -323,8 +313,8 @@ class RandomImageGenerator { $correctionY = 0; if ( $dims['x'] < 0 ) { $correctionX = abs( $dims['x'] ); - } - if ( $dims['y'] < 0 ) { + } + if ( $dims['y'] < 0 ) { $correctionY = abs( $dims['y'] ); } $tSpec['width'] = abs( $dims['x'] ); @@ -332,7 +322,7 @@ class RandomImageGenerator { $tSpec['fill'] = $spec['fill']; $tSpec['draws'] = array(); foreach( $spec['draws'] as $draw ) { - $tDraw = array( + $tDraw = array( 'fill' => $draw['fill'], 'shape' => array() ); @@ -349,13 +339,13 @@ class RandomImageGenerator { /** * Given a matrix and a pair of images, return new position - * @param $matrix: 2x2 rotation matrix + * @param $matrix: 2x2 rotation matrix * @param $x: x-coordinate number * @param $y: y-coordinate number - * @return Array transformed with properties x, y + * @return Array transformed with properties x, y */ private static function matrixMultiply2x2( $matrix, $x, $y ) { - return array( + return array( 'x' => $x * $matrix[0][0] + $y * $matrix[0][1], 'y' => $x * $matrix[1][0] + $y * $matrix[1][1] ); @@ -366,10 +356,10 @@ class RandomImageGenerator { * Based on an image specification, write such an image to disk, using the command line ImageMagick program ('convert'). * * Sample command line: - * $ convert -size 100x60 xc:rgb(90,87,45) \ - * -draw 'fill rgb(12,34,56) polygon 41,39 44,57 50,57 41,39' \ - * -draw 'fill rgb(99,123,231) circle 59,39 56,57' \ - * -draw 'fill rgb(240,12,32) circle 50,21 50,3' filename.png + * $ convert -size 100x60 xc:rgb(90,87,45) \ + * -draw 'fill rgb(12,34,56) polygon 41,39 44,57 50,57 41,39' \ + * -draw 'fill rgb(99,123,231) circle 59,39 56,57' \ + * -draw 'fill rgb(240,12,32) circle 50,21 50,3' filename.png * * @param $spec: spec describing background and shapes to draw * @param $format: file format to write (unused by this method but kept so it has the same signature as writeImageWithApi) diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index 914ab27c..067c731a 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -19,6 +19,7 @@ class MockDatabaseSqlite extends DatabaseSqliteStandalone { /** * @group sqlite + * @group Database */ class DatabaseSqliteTest extends MediaWikiTestCase { var $db; @@ -98,7 +99,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $this->assertEquals( 'sqlite_master', $db->tableName( 'sqlite_master' ) ); $this->assertEquals( 'foobar', $db->tableName( 'bar' ) ); } - + public function testDuplicateTableStructure() { $db = new DatabaseSqliteStandalone( ':memory:' ); $db->query( 'CREATE TABLE foo(foo, barfoo)' ); @@ -119,7 +120,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { 'Create a temporary duplicate only' ); } - + public function testDuplicateTableStructureVirtual() { $db = new DatabaseSqliteStandalone( ':memory:' ); if ( $db->getFulltextSearchModule() != 'FTS3' ) { @@ -191,13 +192,14 @@ class DatabaseSqliteTest extends MediaWikiTestCase { '1.15', '1.16', '1.17', + '1.18', ); // Mismatches for these columns we can safely ignore $ignoredColumns = array( 'user_newtalk.user_last_timestamp', // r84185 ); - + $currentDB = new DatabaseSqliteStandalone( ':memory:' ); $currentDB->sourceFile( "$IP/maintenance/tables.sql" ); $currentTables = $this->getTables( $currentDB ); @@ -254,9 +256,10 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $maint = new FakeMaintenance(); $maint->loadParamsAndArgs( null, array( 'quiet' => 1 ) ); } - + + global $IP; $db = new DatabaseSqliteStandalone( ':memory:' ); - $db->sourceFile( dirname( __FILE__ ) . "/sqlite/tables-$version.sql" ); + $db->sourceFile( "$IP/tests/phpunit/data/db/sqlite/tables-$version.sql" ); $updater = DatabaseUpdater::newForDB( $db, false, $maint ); $updater->doUpdates( array( 'core' ) ); return $db; @@ -266,6 +269,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $list = array_flip( $db->listTables() ); $excluded = array( 'math', // moved out of core in 1.18 + 'trackbacks', // removed from core in 1.19 'searchindex', 'searchindex_content', 'searchindex_segments', diff --git a/tests/phpunit/includes/db/DatabaseTest.php b/tests/phpunit/includes/db/DatabaseTest.php index d480ac6e..672e6645 100644 --- a/tests/phpunit/includes/db/DatabaseTest.php +++ b/tests/phpunit/includes/db/DatabaseTest.php @@ -2,12 +2,20 @@ /** * @group Database + * @group DatabaseBase */ class DatabaseTest extends MediaWikiTestCase { - var $db; + var $db, $functionTest = false; function setUp() { - $this->db = wfGetDB( DB_SLAVE ); + $this->db = wfGetDB( DB_MASTER ); + } + + function tearDown() { + if ( $this->functionTest ) { + $this->dropFunctions(); + $this->functionTest = false; + } } function testAddQuotesNull() { @@ -90,6 +98,26 @@ class DatabaseTest extends MediaWikiTestCase { $sql ); } + /** + * @group Broken + */ + function testStoredFunctions() { + if ( !in_array( wfGetDB( DB_MASTER )->getType(), array( 'mysql', 'postgres' ) ) ) { + $this->markTestSkipped( 'MySQL or Postgres required' ); + } + global $IP; + $this->dropFunctions(); + $this->functionTest = true; + $this->assertTrue( $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" ) ); + $res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ ); + $this->assertEquals( 42, $res->fetchObject()->test ); + } + + private function dropFunctions() { + $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function' + . ( $this->db->getType() == 'postgres' ? '()' : '' ) + ); + } } diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php new file mode 100644 index 00000000..5a4e66d4 --- /dev/null +++ b/tests/phpunit/includes/debug/MWDebugTest.php @@ -0,0 +1,63 @@ +<?php + +class MWDebugTest extends MediaWikiTestCase { + + + function setUp() { + // Make sure MWDebug class is enabled + static $MWDebugEnabled = false; + if( !$MWDebugEnabled ) { + MWDebug::init(); + $MWDebugEnabled = true; + } + /** Clear log before each test */ + MWDebug::clearLog(); + } + + function testAddLog() { + MWDebug::log( 'logging a string' ); + $this->assertEquals( array( array( + 'msg' => 'logging a string', + 'type' => 'log', + 'caller' => __METHOD__ , + ) ), + MWDebug::getLog() + ); + } + + function testAddWarning() { + MWDebug::warning( 'Warning message' ); + $this->assertEquals( array( array( + 'msg' => 'Warning message', + 'type' => 'warn', + 'caller' => 'MWDebug::warning', + ) ), + MWDebug::getLog() + ); + } + + function testAvoidDuplicateDeprecations() { + MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); + MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); + + // assertCount() not available on WMF integration server + $this->assertEquals( 1, + count( MWDebug::getLog() ), + "Only one deprecated warning per function should be kept" + ); + } + + function testAvoidNonConsecutivesDuplicateDeprecations() { + MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); + MWDebug::warning( 'some warning' ); + MWDebug::log( 'we could have logged something too' ); + // Another deprecation + MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); + + // assertCount() not available on WMF integration server + $this->assertEquals( 3, + count( MWDebug::getLog() ), + "Only one deprecated warning per function should be kept" + ); + } +} diff --git a/tests/phpunit/includes/filerepo/FileBackendTest.php b/tests/phpunit/includes/filerepo/FileBackendTest.php new file mode 100644 index 00000000..da44797a --- /dev/null +++ b/tests/phpunit/includes/filerepo/FileBackendTest.php @@ -0,0 +1,1358 @@ +<?php + +/** + * @group FileRepo + * @group FileBackend + */ +class FileBackendTest extends MediaWikiTestCase { + private $backend, $multiBackend; + private $filesToPrune = array(); + private $dirsToPrune = array(); + private static $backendToUse; + + function setUp() { + global $wgFileBackends; + parent::setUp(); + $tmpPrefix = wfTempDir() . '/filebackend-unittest-' . time() . '-' . mt_rand(); + if ( $this->getCliArg( 'use-filebackend=' ) ) { + if ( self::$backendToUse ) { + $this->singleBackend = self::$backendToUse; + } else { + $name = $this->getCliArg( 'use-filebackend=' ); + $useConfig = array(); + foreach ( $wgFileBackends as $conf ) { + if ( $conf['name'] == $name ) { + $useConfig = $conf; + } + } + $useConfig['name'] = 'localtesting'; // swap name + $class = $conf['class']; + self::$backendToUse = new $class( $useConfig ); + $this->singleBackend = self::$backendToUse; + } + } else { + $this->singleBackend = new FSFileBackend( array( + 'name' => 'localtesting', + 'lockManager' => 'fsLockManager', + 'containerPaths' => array( + 'unittest-cont1' => "{$tmpPrefix}-localtesting-cont1", + 'unittest-cont2' => "{$tmpPrefix}-localtesting-cont2" ) + ) ); + } + $this->multiBackend = new FileBackendMultiWrite( array( + 'name' => 'localtesting', + 'lockManager' => 'fsLockManager', + 'backends' => array( + array( + 'name' => 'localmutlitesting1', + 'class' => 'FSFileBackend', + 'lockManager' => 'nullLockManager', + 'containerPaths' => array( + 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti1-cont1", + 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti1-cont2" ), + 'isMultiMaster' => false + ), + array( + 'name' => 'localmutlitesting2', + 'class' => 'FSFileBackend', + 'lockManager' => 'nullLockManager', + 'containerPaths' => array( + 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti2-cont1", + 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti2-cont2" ), + 'isMultiMaster' => true + ) + ) + ) ); + $this->filesToPrune = array(); + } + + private function baseStorePath() { + return 'mwstore://localtesting'; + } + + private function backendClass() { + return get_class( $this->backend ); + } + + /** + * @dataProvider provider_testIsStoragePath + */ + public function testIsStoragePath( $path, $isStorePath ) { + $this->assertEquals( $isStorePath, FileBackend::isStoragePath( $path ), + "FileBackend::isStoragePath on path '$path'" ); + } + + function provider_testIsStoragePath() { + return array( + array( 'mwstore://', true ), + array( 'mwstore://backend', true ), + array( 'mwstore://backend/container', true ), + array( 'mwstore://backend/container/', true ), + array( 'mwstore://backend/container/path', true ), + array( 'mwstore://backend//container/', true ), + array( 'mwstore://backend//container//', true ), + array( 'mwstore://backend//container//path', true ), + array( 'mwstore:///', true ), + array( 'mwstore:/', false ), + array( 'mwstore:', false ), + ); + } + + /** + * @dataProvider provider_testSplitStoragePath + */ + public function testSplitStoragePath( $path, $res ) { + $this->assertEquals( $res, FileBackend::splitStoragePath( $path ), + "FileBackend::splitStoragePath on path '$path'" ); + } + + function provider_testSplitStoragePath() { + return array( + array( 'mwstore://backend/container', array( 'backend', 'container', '' ) ), + array( 'mwstore://backend/container/', array( 'backend', 'container', '' ) ), + array( 'mwstore://backend/container/path', array( 'backend', 'container', 'path' ) ), + array( 'mwstore://backend/container//path', array( 'backend', 'container', '/path' ) ), + array( 'mwstore://backend//container/path', array( null, null, null ) ), + array( 'mwstore://backend//container//path', array( null, null, null ) ), + array( 'mwstore://', array( null, null, null ) ), + array( 'mwstore://backend', array( null, null, null ) ), + array( 'mwstore:///', array( null, null, null ) ), + array( 'mwstore:/', array( null, null, null ) ), + array( 'mwstore:', array( null, null, null ) ) + ); + } + + /** + * @dataProvider provider_normalizeStoragePath + */ + public function testNormalizeStoragePath( $path, $res ) { + $this->assertEquals( $res, FileBackend::normalizeStoragePath( $path ), + "FileBackend::normalizeStoragePath on path '$path'" ); + } + + function provider_normalizeStoragePath() { + return array( + array( 'mwstore://backend/container', 'mwstore://backend/container' ), + array( 'mwstore://backend/container/', 'mwstore://backend/container' ), + array( 'mwstore://backend/container/path', 'mwstore://backend/container/path' ), + array( 'mwstore://backend/container//path', 'mwstore://backend/container/path' ), + array( 'mwstore://backend/container///path', 'mwstore://backend/container/path' ), + array( 'mwstore://backend/container///path//to///obj', 'mwstore://backend/container/path/to/obj', + array( 'mwstore://', null ), + array( 'mwstore://backend', null ), + array( 'mwstore://backend//container/path', null ), + array( 'mwstore://backend//container//path', null ), + array( 'mwstore:///', null ), + array( 'mwstore:/', null ), + array( 'mwstore:', null ), ) + ); + } + + /** + * @dataProvider provider_testParentStoragePath + */ + public function testParentStoragePath( $path, $res ) { + $this->assertEquals( $res, FileBackend::parentStoragePath( $path ), + "FileBackend::parentStoragePath on path '$path'" ); + } + + function provider_testParentStoragePath() { + return array( + array( 'mwstore://backend/container/path/to/obj', 'mwstore://backend/container/path/to' ), + array( 'mwstore://backend/container/path/to', 'mwstore://backend/container/path' ), + array( 'mwstore://backend/container/path', 'mwstore://backend/container' ), + array( 'mwstore://backend/container', null ), + array( 'mwstore://backend/container/path/to/obj/', 'mwstore://backend/container/path/to' ), + array( 'mwstore://backend/container/path/to/', 'mwstore://backend/container/path' ), + array( 'mwstore://backend/container/path/', 'mwstore://backend/container' ), + array( 'mwstore://backend/container/', null ), + ); + } + + /** + * @dataProvider provider_testExtensionFromPath + */ + public function testExtensionFromPath( $path, $res ) { + $this->assertEquals( $res, FileBackend::extensionFromPath( $path ), + "FileBackend::extensionFromPath on path '$path'" ); + } + + function provider_testExtensionFromPath() { + return array( + array( 'mwstore://backend/container/path.txt', 'txt' ), + array( 'mwstore://backend/container/path.svg.png', 'png' ), + array( 'mwstore://backend/container/path', '' ), + array( 'mwstore://backend/container/path.', '' ), + ); + } + + /** + * @dataProvider provider_testStore + */ + public function testStore( $op ) { + $this->filesToPrune[] = $op['src']; + + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestStore( $op ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestStore( $op ); + $this->filesToPrune[] = $op['src']; # avoid file leaking + $this->tearDownFiles(); + } + + function doTestStore( $op ) { + $backendName = $this->backendClass(); + + $source = $op['src']; + $dest = $op['dst']; + $this->prepare( array( 'dir' => dirname( $dest ) ) ); + + file_put_contents( $source, "Unit test file" ); + + if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) { + $this->backend->store( $op ); + } + + $status = $this->backend->doOperation( $op ); + + $this->assertEquals( array(), $status->errors, + "Store from $source to $dest succeeded without warnings ($backendName)." ); + $this->assertEquals( array(), $status->errors, + "Store from $source to $dest succeeded ($backendName)." ); + $this->assertEquals( array( 0 => true ), $status->success, + "Store from $source to $dest has proper 'success' field in Status ($backendName)." ); + $this->assertEquals( true, file_exists( $source ), + "Source file $source still exists ($backendName)." ); + $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ), + "Destination file $dest exists ($backendName)." ); + + $this->assertEquals( filesize( $source ), + $this->backend->getFileSize( array( 'src' => $dest ) ), + "Destination file $dest has correct size ($backendName)." ); + + $props1 = FSFile::getPropsFromPath( $source ); + $props2 = $this->backend->getFileProps( array( 'src' => $dest ) ); + $this->assertEquals( $props1, $props2, + "Source and destination have the same props ($backendName)." ); + } + + public function provider_testStore() { + $cases = array(); + + $tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath(); + $toPath = $this->baseStorePath() . '/unittest-cont1/fun/obj1.txt'; + $op = array( 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath ); + $cases[] = array( + $op, // operation + $tmpName, // source + $toPath, // dest + ); + + $op2 = $op; + $op2['overwrite'] = true; + $cases[] = array( + $op2, // operation + $tmpName, // source + $toPath, // dest + ); + + $op2 = $op; + $op2['overwriteSame'] = true; + $cases[] = array( + $op2, // operation + $tmpName, // source + $toPath, // dest + ); + + return $cases; + } + + /** + * @dataProvider provider_testCopy + */ + public function testCopy( $op ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestCopy( $op ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestCopy( $op ); + $this->tearDownFiles(); + } + + function doTestCopy( $op ) { + $backendName = $this->backendClass(); + + $source = $op['src']; + $dest = $op['dst']; + $this->prepare( array( 'dir' => dirname( $source ) ) ); + $this->prepare( array( 'dir' => dirname( $dest ) ) ); + + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); + $this->assertEquals( array(), $status->errors, + "Creation of file at $source succeeded ($backendName)." ); + + if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) { + $this->backend->copy( $op ); + } + + $status = $this->backend->doOperation( $op ); + + $this->assertEquals( array(), $status->errors, + "Copy from $source to $dest succeeded without warnings ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Copy from $source to $dest succeeded ($backendName)." ); + $this->assertEquals( array( 0 => true ), $status->success, + "Copy from $source to $dest has proper 'success' field in Status ($backendName)." ); + $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $source ) ), + "Source file $source still exists ($backendName)." ); + $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ), + "Destination file $dest exists after copy ($backendName)." ); + + $this->assertEquals( + $this->backend->getFileSize( array( 'src' => $source ) ), + $this->backend->getFileSize( array( 'src' => $dest ) ), + "Destination file $dest has correct size ($backendName)." ); + + $props1 = $this->backend->getFileProps( array( 'src' => $source ) ); + $props2 = $this->backend->getFileProps( array( 'src' => $dest ) ); + $this->assertEquals( $props1, $props2, + "Source and destination have the same props ($backendName)." ); + } + + public function provider_testCopy() { + $cases = array(); + + $source = $this->baseStorePath() . '/unittest-cont1/file.txt'; + $dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt'; + + $op = array( 'op' => 'copy', 'src' => $source, 'dst' => $dest ); + $cases[] = array( + $op, // operation + $source, // source + $dest, // dest + ); + + $op2 = $op; + $op2['overwrite'] = true; + $cases[] = array( + $op2, // operation + $source, // source + $dest, // dest + ); + + $op2 = $op; + $op2['overwriteSame'] = true; + $cases[] = array( + $op2, // operation + $source, // source + $dest, // dest + ); + + return $cases; + } + + /** + * @dataProvider provider_testMove + */ + public function testMove( $op ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestMove( $op ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestMove( $op ); + $this->tearDownFiles(); + } + + private function doTestMove( $op ) { + $backendName = $this->backendClass(); + + $source = $op['src']; + $dest = $op['dst']; + $this->prepare( array( 'dir' => dirname( $source ) ) ); + $this->prepare( array( 'dir' => dirname( $dest ) ) ); + + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); + $this->assertEquals( array(), $status->errors, + "Creation of file at $source succeeded ($backendName)." ); + + if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) { + $this->backend->copy( $op ); + } + + $status = $this->backend->doOperation( $op ); + $this->assertEquals( array(), $status->errors, + "Move from $source to $dest succeeded without warnings ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Move from $source to $dest succeeded ($backendName)." ); + $this->assertEquals( array( 0 => true ), $status->success, + "Move from $source to $dest has proper 'success' field in Status ($backendName)." ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ), + "Source file $source does not still exists ($backendName)." ); + $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ), + "Destination file $dest exists after move ($backendName)." ); + + $this->assertNotEquals( + $this->backend->getFileSize( array( 'src' => $source ) ), + $this->backend->getFileSize( array( 'src' => $dest ) ), + "Destination file $dest has correct size ($backendName)." ); + + $props1 = $this->backend->getFileProps( array( 'src' => $source ) ); + $props2 = $this->backend->getFileProps( array( 'src' => $dest ) ); + $this->assertEquals( false, $props1['fileExists'], + "Source file does not exist accourding to props ($backendName)." ); + $this->assertEquals( true, $props2['fileExists'], + "Destination file exists accourding to props ($backendName)." ); + } + + public function provider_testMove() { + $cases = array(); + + $source = $this->baseStorePath() . '/unittest-cont1/file.txt'; + $dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt'; + + $op = array( 'op' => 'move', 'src' => $source, 'dst' => $dest ); + $cases[] = array( + $op, // operation + $source, // source + $dest, // dest + ); + + $op2 = $op; + $op2['overwrite'] = true; + $cases[] = array( + $op2, // operation + $source, // source + $dest, // dest + ); + + $op2 = $op; + $op2['overwriteSame'] = true; + $cases[] = array( + $op2, // operation + $source, // source + $dest, // dest + ); + + return $cases; + } + + /** + * @dataProvider provider_testDelete + */ + public function testDelete( $op, $withSource, $okStatus ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestDelete( $op, $withSource, $okStatus ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestDelete( $op, $withSource, $okStatus ); + $this->tearDownFiles(); + } + + private function doTestDelete( $op, $withSource, $okStatus ) { + $backendName = $this->backendClass(); + + $source = $op['src']; + $this->prepare( array( 'dir' => dirname( $source ) ) ); + + if ( $withSource ) { + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); + $this->assertEquals( array(), $status->errors, + "Creation of file at $source succeeded ($backendName)." ); + } + + $status = $this->backend->doOperation( $op ); + if ( $okStatus ) { + $this->assertEquals( array(), $status->errors, + "Deletion of file at $source succeeded without warnings ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Deletion of file at $source succeeded ($backendName)." ); + $this->assertEquals( array( 0 => true ), $status->success, + "Deletion of file at $source has proper 'success' field in Status ($backendName)." ); + } else { + $this->assertEquals( false, $status->isOK(), + "Deletion of file at $source failed ($backendName)." ); + } + + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ), + "Source file $source does not exist after move ($backendName)." ); + + $this->assertFalse( + $this->backend->getFileSize( array( 'src' => $source ) ), + "Source file $source has correct size (false) ($backendName)." ); + + $props1 = $this->backend->getFileProps( array( 'src' => $source ) ); + $this->assertFalse( $props1['fileExists'], + "Source file $source does not exist according to props ($backendName)." ); + } + + public function provider_testDelete() { + $cases = array(); + + $source = $this->baseStorePath() . '/unittest-cont1/myfacefile.txt'; + + $op = array( 'op' => 'delete', 'src' => $source ); + $cases[] = array( + $op, // operation + true, // with source + true // succeeds + ); + + $cases[] = array( + $op, // operation + false, // without source + false // fails + ); + + $op['ignoreMissingSource'] = true; + $cases[] = array( + $op, // operation + false, // without source + true // succeeds + ); + + return $cases; + } + + /** + * @dataProvider provider_testCreate + */ + public function testCreate( $op, $alreadyExists, $okStatus, $newSize ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize ); + $this->tearDownFiles(); + } + + private function doTestCreate( $op, $alreadyExists, $okStatus, $newSize ) { + $backendName = $this->backendClass(); + + $dest = $op['dst']; + $this->prepare( array( 'dir' => dirname( $dest ) ) ); + + $oldText = 'blah...blah...waahwaah'; + if ( $alreadyExists ) { + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $oldText, 'dst' => $dest ) ); + $this->assertEquals( array(), $status->errors, + "Creation of file at $dest succeeded ($backendName)." ); + } + + $status = $this->backend->doOperation( $op ); + if ( $okStatus ) { + $this->assertEquals( array(), $status->errors, + "Creation of file at $dest succeeded without warnings ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Creation of file at $dest succeeded ($backendName)." ); + $this->assertEquals( array( 0 => true ), $status->success, + "Creation of file at $dest has proper 'success' field in Status ($backendName)." ); + } else { + $this->assertEquals( false, $status->isOK(), + "Creation of file at $dest failed ($backendName)." ); + } + + $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ), + "Destination file $dest exists after creation ($backendName)." ); + + $props1 = $this->backend->getFileProps( array( 'src' => $dest ) ); + $this->assertEquals( true, $props1['fileExists'], + "Destination file $dest exists according to props ($backendName)." ); + if ( $okStatus ) { // file content is what we saved + $this->assertEquals( $newSize, $props1['size'], + "Destination file $dest has expected size according to props ($backendName)." ); + $this->assertEquals( $newSize, + $this->backend->getFileSize( array( 'src' => $dest ) ), + "Destination file $dest has correct size ($backendName)." ); + } else { // file content is some other previous text + $this->assertEquals( strlen( $oldText ), $props1['size'], + "Destination file $dest has original size according to props ($backendName)." ); + $this->assertEquals( strlen( $oldText ), + $this->backend->getFileSize( array( 'src' => $dest ) ), + "Destination file $dest has original size according to props ($backendName)." ); + } + } + + /** + * @dataProvider provider_testCreate + */ + public function provider_testCreate() { + $cases = array(); + + $dest = $this->baseStorePath() . '/unittest-cont2/myspacefile.txt'; + + $op = array( 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest ); + $cases[] = array( + $op, // operation + false, // no dest already exists + true, // succeeds + strlen( $op['content'] ) + ); + + $op2 = $op; + $op2['content'] = "\n"; + $cases[] = array( + $op2, // operation + false, // no dest already exists + true, // succeeds + strlen( $op2['content'] ) + ); + + $op2 = $op; + $op2['content'] = "fsf\n waf 3kt"; + $cases[] = array( + $op2, // operation + true, // dest already exists + false, // fails + strlen( $op2['content'] ) + ); + + $op2 = $op; + $op2['content'] = "egm'g gkpe gpqg eqwgwqg"; + $op2['overwrite'] = true; + $cases[] = array( + $op2, // operation + true, // dest already exists + true, // succeeds + strlen( $op2['content'] ) + ); + + $op2 = $op; + $op2['content'] = "39qjmg3-qg"; + $op2['overwriteSame'] = true; + $cases[] = array( + $op2, // operation + true, // dest already exists + false, // succeeds + strlen( $op2['content'] ) + ); + + return $cases; + } + + /** + * @dataProvider provider_testConcatenate + */ + public function testConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ) { + $this->filesToPrune[] = $op['dst']; + + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ); + $this->filesToPrune[] = $op['dst']; # avoid file leaking + $this->tearDownFiles(); + } + + public function doTestConcatenate( $params, $srcs, $srcsContent, $alreadyExists, $okStatus ) { + $backendName = $this->backendClass(); + + $expContent = ''; + // Create sources + $ops = array(); + foreach ( $srcs as $i => $source ) { + $this->prepare( array( 'dir' => dirname( $source ) ) ); + $ops[] = array( + 'op' => 'create', // operation + 'dst' => $source, // source + 'content' => $srcsContent[$i] + ); + $expContent .= $srcsContent[$i]; + } + $status = $this->backend->doOperations( $ops ); + + $this->assertEquals( array(), $status->errors, + "Creation of source files succeeded ($backendName)." ); + + $dest = $params['dst']; + if ( $alreadyExists ) { + $ok = file_put_contents( $dest, 'blah...blah...waahwaah' ) !== false; + $this->assertEquals( true, $ok, + "Creation of file at $dest succeeded ($backendName)." ); + } else { + $ok = file_put_contents( $dest, '' ) !== false; + $this->assertEquals( true, $ok, + "Creation of 0-byte file at $dest succeeded ($backendName)." ); + } + + // Combine the files into one + $status = $this->backend->concatenate( $params ); + if ( $okStatus ) { + $this->assertEquals( array(), $status->errors, + "Creation of concat file at $dest succeeded without warnings ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Creation of concat file at $dest succeeded ($backendName)." ); + } else { + $this->assertEquals( false, $status->isOK(), + "Creation of concat file at $dest failed ($backendName)." ); + } + + if ( $okStatus ) { + $this->assertEquals( true, is_file( $dest ), + "Dest concat file $dest exists after creation ($backendName)." ); + } else { + $this->assertEquals( true, is_file( $dest ), + "Dest concat file $dest exists after failed creation ($backendName)." ); + } + + $contents = file_get_contents( $dest ); + $this->assertNotEquals( false, $contents, "File at $dest exists ($backendName)." ); + + if ( $okStatus ) { + $this->assertEquals( $expContent, $contents, + "Concat file at $dest has correct contents ($backendName)." ); + } else { + $this->assertNotEquals( $expContent, $contents, + "Concat file at $dest has correct contents ($backendName)." ); + } + } + + function provider_testConcatenate() { + $cases = array(); + + $rand = mt_rand( 0, 2000000000 ) . time(); + $dest = wfTempDir() . "/randomfile!$rand.txt"; + $srcs = array( + $this->baseStorePath() . '/unittest-cont1/file1.txt', + $this->baseStorePath() . '/unittest-cont1/file2.txt', + $this->baseStorePath() . '/unittest-cont1/file3.txt', + $this->baseStorePath() . '/unittest-cont1/file4.txt', + $this->baseStorePath() . '/unittest-cont1/file5.txt', + $this->baseStorePath() . '/unittest-cont1/file6.txt', + $this->baseStorePath() . '/unittest-cont1/file7.txt', + $this->baseStorePath() . '/unittest-cont1/file8.txt', + $this->baseStorePath() . '/unittest-cont1/file9.txt', + $this->baseStorePath() . '/unittest-cont1/file10.txt' + ); + $content = array( + 'egfage', + 'ageageag', + 'rhokohlr', + 'shgmslkg', + 'kenga', + 'owagmal', + 'kgmae', + 'g eak;g', + 'lkaem;a', + 'legma' + ); + $params = array( 'srcs' => $srcs, 'dst' => $dest ); + + $cases[] = array( + $params, // operation + $srcs, // sources + $content, // content for each source + false, // no dest already exists + true, // succeeds + ); + + $cases[] = array( + $params, // operation + $srcs, // sources + $content, // content for each source + true, // dest already exists + false, // succeeds + ); + + return $cases; + } + + /** + * @dataProvider provider_testGetFileStat + */ + public function testGetFileStat( $path, $content, $alreadyExists ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestGetFileStat( $path, $content, $alreadyExists ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestGetFileStat( $path, $content, $alreadyExists ); + $this->tearDownFiles(); + } + + private function doTestGetFileStat( $path, $content, $alreadyExists ) { + $backendName = $this->backendClass(); + + if ( $alreadyExists ) { + $this->prepare( array( 'dir' => dirname( $path ) ) ); + $status = $this->backend->create( array( 'dst' => $path, 'content' => $content ) ); + $this->assertEquals( array(), $status->errors, + "Creation of file at $path succeeded ($backendName)." ); + + $size = $this->backend->getFileSize( array( 'src' => $path ) ); + $time = $this->backend->getFileTimestamp( array( 'src' => $path ) ); + $stat = $this->backend->getFileStat( array( 'src' => $path ) ); + + $this->assertEquals( strlen( $content ), $size, + "Correct file size of '$path'" ); + $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5, + "Correct file timestamp of '$path'" ); + + $size = $stat['size']; + $time = $stat['mtime']; + $this->assertEquals( strlen( $content ), $size, + "Correct file size of '$path'" ); + $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5, + "Correct file timestamp of '$path'" ); + } else { + $size = $this->backend->getFileSize( array( 'src' => $path ) ); + $time = $this->backend->getFileTimestamp( array( 'src' => $path ) ); + $stat = $this->backend->getFileStat( array( 'src' => $path ) ); + + $this->assertFalse( $size, "Correct file size of '$path'" ); + $this->assertFalse( $time, "Correct file timestamp of '$path'" ); + $this->assertFalse( $stat, "Correct file stat of '$path'" ); + } + } + + function provider_testGetFileStat() { + $cases = array(); + + $base = $this->baseStorePath(); + $cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents", true ); + $cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "", true ); + $cases[] = array( "$base/unittest-cont1/b/some-diff_file.txt", null, false ); + + return $cases; + } + + /** + * @dataProvider provider_testGetFileContents + */ + public function testGetFileContents( $source, $content ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestGetFileContents( $source, $content ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestGetFileContents( $source, $content ); + $this->tearDownFiles(); + } + + public function doTestGetFileContents( $source, $content ) { + $backendName = $this->backendClass(); + + $this->prepare( array( 'dir' => dirname( $source ) ) ); + + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); + $this->assertEquals( array(), $status->errors, + "Creation of file at $source succeeded ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Creation of file at $source succeeded with OK status ($backendName)." ); + + $newContents = $this->backend->getFileContents( array( 'src' => $source, 'latest' => 1 ) ); + $this->assertNotEquals( false, $newContents, + "Read of file at $source succeeded ($backendName)." ); + + $this->assertEquals( $content, $newContents, + "Contents read match data at $source ($backendName)." ); + } + + function provider_testGetFileContents() { + $cases = array(); + + $base = $this->baseStorePath(); + $cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents" ); + $cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "more file contents" ); + + return $cases; + } + + /** + * @dataProvider provider_testGetLocalCopy + */ + public function testGetLocalCopy( $source, $content ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestGetLocalCopy( $source, $content ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestGetLocalCopy( $source, $content ); + $this->tearDownFiles(); + } + + public function doTestGetLocalCopy( $source, $content ) { + $backendName = $this->backendClass(); + + $this->prepare( array( 'dir' => dirname( $source ) ) ); + + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); + $this->assertEquals( array(), $status->errors, + "Creation of file at $source succeeded ($backendName)." ); + + $tmpFile = $this->backend->getLocalCopy( array( 'src' => $source ) ); + $this->assertNotNull( $tmpFile, + "Creation of local copy of $source succeeded ($backendName)." ); + + $contents = file_get_contents( $tmpFile->getPath() ); + $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." ); + } + + function provider_testGetLocalCopy() { + $cases = array(); + + $base = $this->baseStorePath(); + $cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" ); + $cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" ); + + return $cases; + } + + /** + * @dataProvider provider_testGetLocalReference + */ + public function testGetLocalReference( $source, $content ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestGetLocalReference( $source, $content ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestGetLocalReference( $source, $content ); + $this->tearDownFiles(); + } + + private function doTestGetLocalReference( $source, $content ) { + $backendName = $this->backendClass(); + + $this->prepare( array( 'dir' => dirname( $source ) ) ); + + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); + $this->assertEquals( array(), $status->errors, + "Creation of file at $source succeeded ($backendName)." ); + + $tmpFile = $this->backend->getLocalReference( array( 'src' => $source ) ); + $this->assertNotNull( $tmpFile, + "Creation of local copy of $source succeeded ($backendName)." ); + + $contents = file_get_contents( $tmpFile->getPath() ); + $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." ); + } + + function provider_testGetLocalReference() { + $cases = array(); + + $base = $this->baseStorePath(); + $cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" ); + $cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" ); + + return $cases; + } + + /** + * @dataProvider provider_testPrepareAndClean + */ + public function testPrepareAndClean( $path, $isOK ) { + $this->backend = $this->singleBackend; + $this->doTestPrepareAndClean( $path, $isOK ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->doTestPrepareAndClean( $path, $isOK ); + $this->tearDownFiles(); + } + + function provider_testPrepareAndClean() { + $base = $this->baseStorePath(); + return array( + array( "$base/unittest-cont1/a/z/some_file1.txt", true ), + array( "$base/unittest-cont2/a/z/some_file2.txt", true ), + # Specific to FS backend with no basePath field set + #array( "$base/unittest-cont3/a/z/some_file3.txt", false ), + ); + } + + function doTestPrepareAndClean( $path, $isOK ) { + $backendName = $this->backendClass(); + + $status = $this->prepare( array( 'dir' => dirname( $path ) ) ); + if ( $isOK ) { + $this->assertEquals( array(), $status->errors, + "Preparing dir $path succeeded without warnings ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Preparing dir $path succeeded ($backendName)." ); + } else { + $this->assertEquals( false, $status->isOK(), + "Preparing dir $path failed ($backendName)." ); + } + + $status = $this->backend->clean( array( 'dir' => dirname( $path ) ) ); + if ( $isOK ) { + $this->assertEquals( array(), $status->errors, + "Cleaning dir $path succeeded without warnings ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Cleaning dir $path succeeded ($backendName)." ); + } else { + $this->assertEquals( false, $status->isOK(), + "Cleaning dir $path failed ($backendName)." ); + } + } + + // @TODO: testSecure + + public function testDoOperations() { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestDoOperations(); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestDoOperations(); + $this->tearDownFiles(); + + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestDoOperationsFailing(); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestDoOperationsFailing(); + $this->tearDownFiles(); + + // @TODO: test some cases where the ops should fail + } + + function doTestDoOperations() { + $base = $this->baseStorePath(); + + $fileA = "$base/unittest-cont1/a/b/fileA.txt"; + $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq'; + $fileB = "$base/unittest-cont1/a/b/fileB.txt"; + $fileBContents = 'g-jmq3gpqgt3qtg q3GT '; + $fileC = "$base/unittest-cont1/a/b/fileC.txt"; + $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag'; + $fileD = "$base/unittest-cont1/a/b/fileD.txt"; + + $this->prepare( array( 'dir' => dirname( $fileA ) ) ); + $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) ); + $this->prepare( array( 'dir' => dirname( $fileB ) ) ); + $this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) ); + $this->prepare( array( 'dir' => dirname( $fileC ) ) ); + $this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) ); + + $status = $this->backend->doOperations( array( + array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ), + // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>) + array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ), + // Now: A:<A>, B:<B>, C:<A>, D:<empty> + array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD, 'overwrite' => 1 ), + // Now: A:<A>, B:<B>, C:<empty>, D:<A> + array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC ), + // Now: A:<A>, B:<empty>, C:<B>, D:<A> + array( 'op' => 'move', 'src' => $fileD, 'dst' => $fileA, 'overwriteSame' => 1 ), + // Now: A:<A>, B:<empty>, C:<B>, D:<empty> + array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileA, 'overwrite' => 1 ), + // Now: A:<B>, B:<empty>, C:<empty>, D:<empty> + array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC ), + // Now: A:<B>, B:<empty>, C:<B>, D:<empty> + array( 'op' => 'move', 'src' => $fileA, 'dst' => $fileC, 'overwriteSame' => 1 ), + // Now: A:<empty>, B:<empty>, C:<B>, D:<empty> + array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ), + // Does nothing + array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ), + // Does nothing + array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ), + // Does nothing + array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ), + // Does nothing + array( 'op' => 'null' ), + // Does nothing + ) ); + + $this->assertEquals( array(), $status->errors, "Operation batch succeeded" ); + $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" ); + $this->assertEquals( 13, count( $status->success ), + "Operation batch has correct success array" ); + + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileA ) ), + "File does not exist at $fileA" ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ), + "File does not exist at $fileB" ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ), + "File does not exist at $fileD" ); + + $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ), + "File exists at $fileC" ); + $this->assertEquals( $fileBContents, + $this->backend->getFileContents( array( 'src' => $fileC ) ), + "Correct file contents of $fileC" ); + $this->assertEquals( strlen( $fileBContents ), + $this->backend->getFileSize( array( 'src' => $fileC ) ), + "Correct file size of $fileC" ); + $this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ), + $this->backend->getFileSha1Base36( array( 'src' => $fileC ) ), + "Correct file SHA-1 of $fileC" ); + } + + function doTestDoOperationsFailing() { + $base = $this->baseStorePath(); + + $fileA = "$base/unittest-cont2/a/b/fileA.txt"; + $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq'; + $fileB = "$base/unittest-cont2/a/b/fileB.txt"; + $fileBContents = 'g-jmq3gpqgt3qtg q3GT '; + $fileC = "$base/unittest-cont2/a/b/fileC.txt"; + $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag'; + $fileD = "$base/unittest-cont2/a/b/fileD.txt"; + + $this->prepare( array( 'dir' => dirname( $fileA ) ) ); + $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) ); + $this->prepare( array( 'dir' => dirname( $fileB ) ) ); + $this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) ); + $this->prepare( array( 'dir' => dirname( $fileC ) ) ); + $this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) ); + + $status = $this->backend->doOperations( array( + array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ), + // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>) + array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ), + // Now: A:<A>, B:<B>, C:<A>, D:<empty> + array( 'op' => 'copy', 'src' => $fileB, 'dst' => $fileD, 'overwrite' => 1 ), + // Now: A:<A>, B:<B>, C:<A>, D:<B> + array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD ), + // Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed) + array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC, 'overwriteSame' => 1 ), + // Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed) + array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileA, 'overwrite' => 1 ), + // Now: A:<B>, B:<empty>, C:<A>, D:<empty> + array( 'op' => 'delete', 'src' => $fileD ), + // Now: A:<B>, B:<empty>, C:<A>, D:<empty> + array( 'op' => 'null' ), + // Does nothing + ), array( 'force' => 1 ) ); + + $this->assertNotEquals( array(), $status->errors, "Operation had warnings" ); + $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" ); + $this->assertEquals( 8, count( $status->success ), + "Operation batch has correct success array" ); + + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ), + "File does not exist at $fileB" ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ), + "File does not exist at $fileD" ); + + $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileA ) ), + "File does not exist at $fileA" ); + $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ), + "File exists at $fileC" ); + $this->assertEquals( $fileBContents, + $this->backend->getFileContents( array( 'src' => $fileA ) ), + "Correct file contents of $fileA" ); + $this->assertEquals( strlen( $fileBContents ), + $this->backend->getFileSize( array( 'src' => $fileA ) ), + "Correct file size of $fileA" ); + $this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ), + $this->backend->getFileSha1Base36( array( 'src' => $fileA ) ), + "Correct file SHA-1 of $fileA" ); + } + + public function testGetFileList() { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestGetFileList(); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestGetFileList(); + $this->tearDownFiles(); + } + + private function doTestGetFileList() { + $backendName = $this->backendClass(); + + $base = $this->baseStorePath(); + $files = array( + "$base/unittest-cont1/test1.txt", + "$base/unittest-cont1/test2.txt", + "$base/unittest-cont1/test3.txt", + "$base/unittest-cont1/subdir1/test1.txt", + "$base/unittest-cont1/subdir1/test2.txt", + "$base/unittest-cont1/subdir2/test3.txt", + "$base/unittest-cont1/subdir2/test4.txt", + "$base/unittest-cont1/subdir2/subdir/test1.txt", + "$base/unittest-cont1/subdir2/subdir/test2.txt", + "$base/unittest-cont1/subdir2/subdir/test3.txt", + "$base/unittest-cont1/subdir2/subdir/test4.txt", + "$base/unittest-cont1/subdir2/subdir/test5.txt", + "$base/unittest-cont1/subdir2/subdir/sub/test0.txt", + "$base/unittest-cont1/subdir2/subdir/sub/120-px-file.txt", + ); + + // Add the files + $ops = array(); + foreach ( $files as $file ) { + $this->prepare( array( 'dir' => dirname( $file ) ) ); + $ops[] = array( 'op' => 'create', 'content' => 'xxy', 'dst' => $file ); + } + $status = $this->backend->doOperations( $ops ); + $this->assertEquals( array(), $status->errors, + "Creation of files succeeded ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Creation of files succeeded with OK status ($backendName)." ); + + // Expected listing + $expected = array( + "test1.txt", + "test2.txt", + "test3.txt", + "subdir1/test1.txt", + "subdir1/test2.txt", + "subdir2/test3.txt", + "subdir2/test4.txt", + "subdir2/subdir/test1.txt", + "subdir2/subdir/test2.txt", + "subdir2/subdir/test3.txt", + "subdir2/subdir/test4.txt", + "subdir2/subdir/test5.txt", + "subdir2/subdir/sub/test0.txt", + "subdir2/subdir/sub/120-px-file.txt", + ); + sort( $expected ); + + // Actual listing (no trailing slash) + $list = array(); + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); + + // Actual listing (with trailing slash) + $list = array(); + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); + + // Expected listing + $expected = array( + "test1.txt", + "test2.txt", + "test3.txt", + "test4.txt", + "test5.txt", + "sub/test0.txt", + "sub/120-px-file.txt", + ); + sort( $expected ); + + // Actual listing (no trailing slash) + $list = array(); + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/subdir2/subdir" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); + + // Actual listing (with trailing slash) + $list = array(); + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/subdir2/subdir/" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); + + // Actual listing (using iterator second time) + $list = array(); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct file listing ($backendName), second iteration." ); + + foreach ( $files as $file ) { // clean up + $this->backend->doOperation( array( 'op' => 'delete', 'src' => $file ) ); + } + + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/not/exists" ) ); + foreach ( $iter as $iter ) {} // no errors + } + + // test helper wrapper for backend prepare() function + private function prepare( array $params ) { + $this->dirsToPrune[] = $params['dir']; + return $this->backend->prepare( $params ); + } + + function tearDownFiles() { + foreach ( $this->filesToPrune as $file ) { + @unlink( $file ); + } + $containers = array( 'unittest-cont1', 'unittest-cont2', 'unittest-cont3' ); + foreach ( $containers as $container ) { + $this->deleteFiles( $container ); + } + foreach ( $this->dirsToPrune as $dir ) { + $this->recursiveClean( $dir ); + } + $this->filesToPrune = $this->dirsToPrune = array(); + } + + private function deleteFiles( $container ) { + $base = $this->baseStorePath(); + $iter = $this->backend->getFileList( array( 'dir' => "$base/$container" ) ); + if ( $iter ) { + foreach ( $iter as $file ) { + $this->backend->delete( array( 'src' => "$base/$container/$file" ), array( 'force' => 1 ) ); + } + } + } + + private function recursiveClean( $dir ) { + do { + if ( !$this->backend->clean( array( 'dir' => $dir ) )->isOK() ) { + break; + } + } while ( $dir = FileBackend::parentStoragePath( $dir ) ); + } + + function tearDown() { + parent::tearDown(); + } +} diff --git a/tests/phpunit/includes/filerepo/FileRepoTest.php b/tests/phpunit/includes/filerepo/FileRepoTest.php new file mode 100644 index 00000000..0f023138 --- /dev/null +++ b/tests/phpunit/includes/filerepo/FileRepoTest.php @@ -0,0 +1,41 @@ +<?php + +class FileRepoTest extends MediaWikiTestCase { + + /** + * @expectedException MWException + */ + function testFileRepoConstructionOptionCanNotBeNull() { + $f = new FileRepo(); + } + /** + * @expectedException MWException + */ + function testFileRepoConstructionOptionCanNotBeAnEmptyArray() { + $f = new FileRepo( array() ); + } + /** + * @expectedException MWException + */ + function testFileRepoConstructionOptionNeedNameKey() { + $f = new FileRepo( array( + 'backend' => 'foobar' + ) ); + } + /** + * @expectedException MWException + */ + function testFileRepoConstructionOptionNeedBackendKey() { + $f = new FileRepo( array( + 'name' => 'foobar' + ) ); + } + + function testFileRepoConstructionWithRequiredOptions() { + $f = new FileRepo( array( + 'name' => 'FileRepoTestRepository', + 'backend' => 'local-backend', + )); + $this->assertInstanceOf( 'FileRepo', $f ); + } +} diff --git a/tests/phpunit/includes/filerepo/StoreBatchTest.php b/tests/phpunit/includes/filerepo/StoreBatchTest.php new file mode 100644 index 00000000..6abceeb3 --- /dev/null +++ b/tests/phpunit/includes/filerepo/StoreBatchTest.php @@ -0,0 +1,122 @@ +<?php +/** + * @group FileRepo + */ +class StoreBatchTest extends MediaWikiTestCase { + + public function setUp() { + global $wgFileBackends; + parent::setUp(); + + # Forge a FSRepo object to not have to rely on local wiki settings + $tmpPrefix = wfTempDir() . '/storebatch-test-' . time() . '-' . mt_rand(); + if ( $this->getCliArg( 'use-filebackend=' ) ) { + $name = $this->getCliArg( 'use-filebackend=' ); + $useConfig = array(); + foreach ( $wgFileBackends as $conf ) { + if ( $conf['name'] == $name ) { + $useConfig = $conf; + } + } + $useConfig['name'] = 'local-testing'; // swap name + $class = $useConfig['class']; + $backend = new $class( $useConfig ); + } else { + $backend = new FSFileBackend( array( + 'name' => 'local-testing', + 'lockManager' => 'nullLockManager', + 'containerPaths' => array( + 'unittests-public' => "{$tmpPrefix}-public", + 'unittests-thumb' => "{$tmpPrefix}-thumb", + 'unittests-temp' => "{$tmpPrefix}-temp", + 'unittests-deleted' => "{$tmpPrefix}-deleted", + ) + ) ); + } + $this->repo = new FileRepo( array( + 'name' => 'unittests', + 'backend' => $backend + ) ); + + $this->date = gmdate( "YmdHis" ); + $this->createdFiles = array(); + } + + /** + * Store a file or virtual URL source into a media file name. + * + * @param $originalName string The title of the image + * @param $srcPath string The filepath or virtual URL + * @param $flags integer Flags to pass into repo::store(). + */ + private function storeit($originalName, $srcPath, $flags) { + $hashPath = $this->repo->getHashPath( $originalName ); + $dstRel = "$hashPath{$this->date}!$originalName"; + $dstUrlRel = $hashPath . $this->date . '!' . rawurlencode( $originalName ); + + $result = $this->repo->store( $srcPath, 'temp', $dstRel, $flags ); + $result->value = $this->repo->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel; + $this->createdFiles[] = $result->value; + return $result; + } + + /** + * Test storing a file using different flags. + * + * @param $fn string The title of the image + * @param $infn string The name of the file (in the filesystem) + * @param $otherfn string The name of the different file (in the filesystem) + * @param $fromrepo logical 'true' if we want to copy from a virtual URL out of the Repo. + */ + private function storecohort($fn, $infn, $otherfn, $fromrepo) { + $f = $this->storeit( $fn, $infn, 0 ); + $this->assertTrue( $f->isOK(), 'failed to store a new file' ); + $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" ); + if ( $fromrepo ) { + $f = $this->storeit( "Other-$fn", $infn, FileRepo::OVERWRITE); + $infn = $f->value; + } + // This should work because we're allowed to overwrite + $f = $this->storeit( $fn, $infn, FileRepo::OVERWRITE ); + $this->assertTrue( $f->isOK(), 'We should be allowed to overwrite' ); + $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" ); + // This should fail because we're overwriting. + $f = $this->storeit( $fn, $infn, 0 ); + $this->assertFalse( $f->isOK(), 'We should not be allowed to overwrite' ); + $this->assertEquals( $f->failCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 0 , "counts wrong {$f->successCount} {$f->failCount}" ); + // This should succeed because we're overwriting the same content. + $f = $this->storeit( $fn, $infn, FileRepo::OVERWRITE_SAME ); + $this->assertTrue( $f->isOK(), 'We should be able to overwrite the same content' ); + $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" ); + // This should fail because we're overwriting different content. + if ( $fromrepo ) { + $f = $this->storeit( "Other-$fn", $otherfn, FileRepo::OVERWRITE); + $otherfn = $f->value; + } + $f = $this->storeit( $fn, $otherfn, FileRepo::OVERWRITE_SAME ); + $this->assertFalse( $f->isOK(), 'We should not be allowed to overwrite different content' ); + $this->assertEquals( $f->failCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 0 , "counts wrong {$f->successCount} {$f->failCount}" ); + } + + public function teststore() { + global $IP; + $this->storecohort( "Test1.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", false ); + $this->storecohort( "Test2.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", true ); + } + + public function tearDown() { + $this->repo->cleanupBatch( $this->createdFiles ); // delete files + foreach ( $this->createdFiles as $tmp ) { // delete dirs + $tmp = $this->repo->resolveVirtualUrl( $tmp ); + while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) { + $this->repo->getBackend()->clean( array( 'dir' => $tmp ) ); + } + } + parent::tearDown(); + } +} diff --git a/tests/phpunit/includes/json/ServicesJsonTest.php b/tests/phpunit/includes/json/ServicesJsonTest.php new file mode 100644 index 00000000..8f2421a2 --- /dev/null +++ b/tests/phpunit/includes/json/ServicesJsonTest.php @@ -0,0 +1,93 @@ +<?php +/* + * Test cases for our Services_Json library. Requires PHP json support as well, + * so we can compare output + */ +class ServicesJsonTest extends MediaWikiTestCase { + /** + * Test to make sure core json_encode() and our Services_Json()->encode() + * produce the same output + * + * @dataProvider provideValuesToEncode + */ + public function testJsonEncode( $input, $desc ) { + if ( !function_exists( 'json_encode' ) ) { + $this->markTestIncomplete( 'No PHP json support, unable to test' ); + return; + } elseif( strtolower( json_encode( "\xf0\xa0\x80\x80" ) ) != '"\ud840\udc00"' ) { + $this->markTestIncomplete( 'Have buggy PHP json support, unable to test' ); + return; + } else { + $jsonObj = new Services_JSON(); + $this->assertEquals( + $jsonObj->encode( $input ), + json_encode( $input ), + $desc + ); + } + } + + /** + * Test to make sure core json_decode() and our Services_Json()->decode() + * produce the same output + * + * @dataProvider provideValuesToDecode + */ + public function testJsonDecode( $input, $desc ) { + if ( !function_exists( 'json_decode' ) ) { + $this->markTestIncomplete( 'No PHP json support, unable to test' ); + return; + } else { + $jsonObj = new Services_JSON(); + $this->assertEquals( + $jsonObj->decode( $input ), + json_decode( $input ), + $desc + ); + } + } + + function provideValuesToEncode() { + $obj = new stdClass(); + $obj->property = 'value'; + $obj->property2 = null; + $obj->property3 = 1.234; + return array( + array( 1, 'basic integer' ), + array( -1, 'negative integer' ), + array( 1.1, 'basic float' ), + array( true, 'basic bool true' ), + array( false, 'basic bool false' ), + array( 'some string', 'basic string test' ), + array( "some string\nwith newline", 'newline string test' ), + array( '♥ü', 'unicode string test' ), + array( array( 'some', 'string', 'values' ), 'basic array of strings' ), + array( array( 'key1' => 'val1', 'key2' => 'val2' ), 'array with string keys' ), + array( array( 1 => 'val1', 3 => 'val2', '2' => 'val3' ), 'out of order numbered array test' ), + array( array(), 'empty array test' ), + array( $obj, 'basic object test' ), + array( new stdClass, 'empty object test' ), + array( null, 'null test' ), + ); + } + + function provideValuesToDecode() { + return array( + array( '1', 'basic integer' ), + array( '-1', 'negative integer' ), + array( '1.1', 'basic float' ), + array( '1.1e1', 'scientific float' ), + array( 'true', 'basic bool true' ), + array( 'false', 'basic bool false' ), + array( '"some string"', 'basic string test' ), + array( '"some string\nwith newline"', 'newline string test' ), + array( '"♥ü"', 'unicode character string test' ), + array( '"\u2665"', 'unicode \\u string test' ), + array( '["some","string","values"]', 'basic array of strings' ), + array( '[]', 'empty array test' ), + array( '{"key":"value"}', 'Basic key => value test' ), + array( '{}', 'empty object test' ), + array( 'null', 'null test' ), + ); + } +} diff --git a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php index aa05500e..d2bfeedf 100644 --- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php +++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php @@ -84,6 +84,13 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { // And also per spec unicode char escape values should work in identifiers, // as long as it's a valid char. In future it might get normalized. array( "var Ka\\u015dSkatolVal = {}", 'var Ka\\u015dSkatolVal={}'), + + /* Some structures that might look invalid at first sight */ + array( "var a = 5.;", "var a=5.;" ), + array( "5.0.toString();", "5.0.toString();" ), + array( "5..toString();", "5..toString();" ), + array( "5...toString();", false ), + array( "5.\n.toString();", '5..toString();' ), ); } @@ -102,4 +109,40 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { $this->assertEquals( $expectedOutput, $minified, "Minified output should be in the form expected." ); } + + /** + * @dataProvider provideBug32548 + */ + function testBug32548Exponent($num) { + // Long line breaking was being incorrectly done between the base and + // exponent part of a number, causing a syntax error. The line should + // instead break at the start of the number. + $prefix = 'var longVarName' . str_repeat('_', 973) . '='; + $suffix = ',shortVarName=0;'; + + $input = $prefix . $num . $suffix; + $expected = $prefix . "\n" . $num . $suffix; + + $minified = JavaScriptMinifier::minify( $input ); + + $this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent"); + } + + function provideBug32548() { + return array( + array( + // This one gets interpreted all together by the prior code; + // no break at the 'E' happens. + '1.23456789E55', + ), + array( + // This one breaks under the bad code; splits between 'E' and '+' + '1.23456789E+5', + ), + array( + // This one breaks under the bad code; splits between 'E' and '-' + '1.23456789E-5', + ), + ); + } } diff --git a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php index a0d5cd86..f4f52dd8 100644 --- a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php +++ b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php @@ -14,10 +14,15 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { * translation (to en) where XMP should win. */ public function testMultilingualCascade() { - global $wgShowEXIF; - if ( !$wgShowEXIF ) { - $this->markTestIncomplete( "This test needs the exif extension." ); + if ( !wfDl( 'exif' ) ) { + $this->markTestSkipped( "This test needs the exif extension." ); + } + if ( !wfDl( 'xml' ) ) { + $this->markTestSkipped( "This test needs the xml extension." ); } + global $wgShowEXIF; + $oldExif = $wgShowEXIF; + $wgShowEXIF = true; $meta = BitmapMetadataHandler::Jpeg( $this->filePath . '/Xmp-exif-multilingual_test.jpg' ); @@ -32,6 +37,8 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { 'Did not extract any ImageDescription info?!' ); $this->assertEquals( $expected, $meta['ImageDescription'] ); + + $wgShowEXIF = $oldExif; } /** @@ -49,6 +56,16 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { $meta['JPEGFileComment'][0] ); } + /** + * Make sure a bad iptc block doesn't stop the other metadata + * from being extracted. + */ + public function testBadIPTC() { + $meta = BitmapMetadataHandler::Jpeg( $this->filePath . + 'iptc-invalid-psir.jpg' ); + $this->assertEquals( 'Created with GIMP', $meta['JPEGFileComment'][0] ); + } + public function testIPTCDates() { $meta = BitmapMetadataHandler::Jpeg( $this->filePath . 'iptc-timetest.jpg' ); @@ -95,6 +112,9 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { } public function testPNGXMP() { + if ( !wfDl( 'xml' ) ) { + $this->markTestSkipped( "This test needs the xml extension." ); + } $handler = new BitmapMetadataHandler(); $result = $handler->png( $this->filePath . 'xmp.png' ); $expected = array ( diff --git a/tests/phpunit/includes/media/BitmapScalingTest.php b/tests/phpunit/includes/media/BitmapScalingTest.php index 5bcd3232..11d9dc47 100644 --- a/tests/phpunit/includes/media/BitmapScalingTest.php +++ b/tests/phpunit/includes/media/BitmapScalingTest.php @@ -3,13 +3,16 @@ class BitmapScalingTest extends MediaWikiTestCase { function setUp() { - global $wgMaxImageArea; + global $wgMaxImageArea, $wgCustomConvertCommand; $this->oldMaxImageArea = $wgMaxImageArea; + $this->oldCustomConvertCommand = $wgCustomConvertCommand; $wgMaxImageArea = 1.25e7; // 3500x3500 + $wgCustomConvertCommand = 'dummy'; // Set so that we don't get client side rendering } function tearDown() { - global $wgMaxImageArea; + global $wgMaxImageArea, $wgCustomConvertCommand; $wgMaxImageArea = $this->oldMaxImageArea; + $wgCustomConvertCommand = $this->oldCustomConvertCommand; } /** * @dataProvider provideNormaliseParams @@ -105,14 +108,22 @@ class BitmapScalingTest extends MediaWikiTestCase { $file = new FakeDimensionFile( array( 4000, 4000 ) ); $handler = new BitmapHandler; $params = array( 'width' => '3700' ); // Still bigger than max size. - $this->assertFalse( $handler->normaliseParams( $file, $params ) ); + $this->assertEquals( 'TransformParameterError', + get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) ); } function testTooBigMustRenderImage() { $file = new FakeDimensionFile( array( 4000, 4000 ) ); $file->mustRender = true; $handler = new BitmapHandler; $params = array( 'width' => '5000' ); // Still bigger than max size. - $this->assertFalse( $handler->normaliseParams( $file, $params ) ); + $this->assertEquals( 'TransformParameterError', + get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) ); + } + + function testImageArea() { + $file = new FakeDimensionFile( array( 7, 9 ) ); + $handler = new BitmapHandler; + $this->assertEquals( 63, $handler->getImageArea( $file ) ); } } @@ -120,7 +131,8 @@ class FakeDimensionFile extends File { public $mustRender = false; public function __construct( $dimensions ) { - parent::__construct( Title::makeTitle( NS_FILE, 'Test' ), null ); + parent::__construct( Title::makeTitle( NS_FILE, 'Test' ), + new NullRepo( null ) ); $this->dimensions = $dimensions; } @@ -133,4 +145,7 @@ class FakeDimensionFile extends File { public function mustRender() { return $this->mustRender; } + public function getPath() { + return ''; + } } diff --git a/tests/phpunit/includes/media/ExifBitmapTest.php b/tests/phpunit/includes/media/ExifBitmapTest.php index 4282d3c8..b2f6b7ba 100644 --- a/tests/phpunit/includes/media/ExifBitmapTest.php +++ b/tests/phpunit/includes/media/ExifBitmapTest.php @@ -1,4 +1,5 @@ <?php + class ExifBitmapTest extends MediaWikiTestCase { public function setUp() { @@ -17,42 +18,23 @@ class ExifBitmapTest extends MediaWikiTestCase { } public function testIsOldBroken() { - if ( !wfDl( 'exif' ) ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::OLD_BROKEN_FILE ); $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res ); } public function testIsBrokenFile() { - global $wgShowEXIF; - if ( !$wgShowEXIF ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::BROKEN_FILE ); $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res ); } public function testIsInvalid() { - global $wgShowEXIF; - if ( !$wgShowEXIF ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $res = $this->handler->isMetadataValid( null, 'Something Invalid Here.' ); $this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res ); } public function testGoodMetadata() { - global $wgShowEXIF; - if ( !$wgShowEXIF ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}'; $res = $this->handler->isMetadataValid( null, $meta ); $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res ); } public function testIsOldGood() { - global $wgShowEXIF; - if ( !$wgShowEXIF ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}'; $res = $this->handler->isMetadataValid( null, $meta ); $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res ); @@ -60,10 +42,6 @@ class ExifBitmapTest extends MediaWikiTestCase { // Handle metadata from paged tiff handler (gotten via instant commons) // gracefully. public function testPagedTiffHandledGracefully() { - global $wgShowEXIF; - if ( !$wgShowEXIF ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $meta = 'a:6:{s:9:"page_data";a:1:{i:1;a:5:{s:5:"width";i:643;s:6:"height";i:448;s:5:"alpha";s:4:"true";s:4:"page";i:1;s:6:"pixels";i:288064;}}s:10:"page_count";i:1;s:10:"first_page";i:1;s:9:"last_page";i:1;s:4:"exif";a:9:{s:10:"ImageWidth";i:643;s:11:"ImageLength";i:448;s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:4;s:12:"RowsPerStrip";i:50;s:19:"PlanarConfiguration";i:1;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}s:21:"TIFF_METADATA_VERSION";s:3:"1.4";}'; $res = $this->handler->isMetadataValid( null, $meta ); $this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res ); diff --git a/tests/phpunit/includes/media/ExifRotationTest.php b/tests/phpunit/includes/media/ExifRotationTest.php index 639091d0..25149a05 100644 --- a/tests/phpunit/includes/media/ExifRotationTest.php +++ b/tests/phpunit/includes/media/ExifRotationTest.php @@ -5,15 +5,26 @@ */ class ExifRotationTest extends MediaWikiTestCase { + /** track directories creations. Content will be deleted. */ + private $createdDirs = array(); + function setUp() { parent::setUp(); - $this->filePath = dirname( __FILE__ ) . '/../../data/media/'; $this->handler = new BitmapHandler(); - $this->repo = new FSRepo(array( - 'name' => 'temp', - 'directory' => wfTempDir() . '/exif-test-' . time() . '-' . mt_rand(), - 'url' => 'http://localhost/thumbtest' - )); + $filePath = dirname( __FILE__ ) . '/../../data/media'; + + $tmpDir = wfTempDir() . '/exif-test-' . time() . '-' . mt_rand(); + $this->createdDirs[] = $tmpDir; + + $this->repo = new FSRepo( array( + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', + 'backend' => new FSFileBackend( array( + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', + 'containerPaths' => array( 'temp-thumb' => $tmpDir, 'data' => $filePath ) + ) ) + ) ); if ( !wfDl( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } @@ -25,10 +36,23 @@ class ExifRotationTest extends MediaWikiTestCase { $this->oldAuto = $wgEnableAutoRotation; $wgEnableAutoRotation = true; } + public function tearDown() { global $wgShowEXIF, $wgEnableAutoRotation; $wgShowEXIF = $this->show; $wgEnableAutoRotation = $this->oldAuto; + + $this->tearDownFiles(); + } + + private function tearDownFiles() { + foreach( $this->createdDirs as $dir ) { + wfRecursiveRemoveDir( $dir ); + } + } + + function __destruct() { + $this->tearDownFiles(); } /** @@ -39,7 +63,7 @@ class ExifRotationTest extends MediaWikiTestCase { if ( !BitmapHandler::canRotate() ) { $this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." ); } - $file = UnregisteredLocalFile::newFromPath( $this->filePath . $name, $type ); + $file = $this->dataFile( $name, $type ); $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" ); $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" ); } @@ -66,13 +90,13 @@ class ExifRotationTest extends MediaWikiTestCase { throw new MWException('bogus test data format ' . $size); } - $file = $this->localFile( $name, $type ); - $thumb = $file->transform( $params, File::RENDER_NOW ); + $file = $this->dataFile( $name, $type ); + $thumb = $file->transform( $params, File::RENDER_NOW | File::RENDER_FORCE ); $this->assertEquals( $out[0], $thumb->getWidth(), "$name: thumb reported width check for $size" ); $this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" ); - $gis = getimagesize( $thumb->getPath() ); + $gis = getimagesize( $thumb->getLocalCopyPath() ); if ($out[0] > $info['width']) { // Physical image won't be scaled bigger than the original. $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size"); @@ -84,8 +108,9 @@ class ExifRotationTest extends MediaWikiTestCase { } } - private function localFile( $name, $type ) { - return new UnregisteredLocalFile( false, $this->repo, $this->filePath . $name, $type ); + private function dataFile( $name, $type ) { + return new UnregisteredLocalFile( false, $this->repo, + "mwstore://localtesting/data/$name", $type ); } function providerFiles() { @@ -129,7 +154,7 @@ class ExifRotationTest extends MediaWikiTestCase { global $wgEnableAutoRotation; $wgEnableAutoRotation = false; - $file = UnregisteredLocalFile::newFromPath( $this->filePath . $name, $type ); + $file = $this->dataFile( $name, $type ); $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" ); $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" ); @@ -158,13 +183,13 @@ class ExifRotationTest extends MediaWikiTestCase { throw new MWException('bogus test data format ' . $size); } - $file = $this->localFile( $name, $type ); - $thumb = $file->transform( $params, File::RENDER_NOW ); + $file = $this->dataFile( $name, $type ); + $thumb = $file->transform( $params, File::RENDER_NOW | File::RENDER_FORCE ); $this->assertEquals( $out[0], $thumb->getWidth(), "$name: thumb reported width check for $size" ); $this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" ); - $gis = getimagesize( $thumb->getPath() ); + $gis = getimagesize( $thumb->getLocalCopyPath() ); if ($out[0] > $info['width']) { // Physical image won't be scaled bigger than the original. $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size"); @@ -242,7 +267,7 @@ class ExifRotationTest extends MediaWikiTestCase { array( 270, array( self::TEST_HEIGHT, self::TEST_WIDTH ) - ), + ), ); } } diff --git a/tests/phpunit/includes/media/ExifTest.php b/tests/phpunit/includes/media/ExifTest.php index 9b490e92..b39c15e4 100644 --- a/tests/phpunit/includes/media/ExifTest.php +++ b/tests/phpunit/includes/media/ExifTest.php @@ -4,6 +4,9 @@ class ExifTest extends MediaWikiTestCase { public function setUp() { $this->mediaPath = dirname( __FILE__ ) . '/../../data/media/'; + if ( !wfDl( 'exif' ) ) { + $this->markTestSkipped( "This test needs the exif extension." ); + } global $wgShowEXIF; $this->showExif = $wgShowEXIF; $wgShowEXIF = true; @@ -14,9 +17,6 @@ class ExifTest extends MediaWikiTestCase { } public function testGPSExtraction() { - if ( !wfDl( 'exif' ) ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $filename = $this->mediaPath . 'exif-gps.jpg'; $seg = JpegMetadataExtractor::segmentSplitter( $filename ); @@ -32,9 +32,6 @@ class ExifTest extends MediaWikiTestCase { $this->assertEquals( $expected, $data, '', 0.0000000001 ); } public function testUnicodeUserComment() { - if ( !wfDl( 'exif' ) ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $filename = $this->mediaPath . 'exif-user-comment.jpg'; $seg = JpegMetadataExtractor::segmentSplitter( $filename ); diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php index db36dea3..8a632f52 100644 --- a/tests/phpunit/includes/media/FormatMetadataTest.php +++ b/tests/phpunit/includes/media/FormatMetadataTest.php @@ -1,13 +1,31 @@ <?php class FormatMetadataTest extends MediaWikiTestCase { - public function testInvalidDate() { - global $wgShowEXIF; - if ( !$wgShowEXIF ) { - $this->markTestIncomplete( "This test needs the exif extension." ); + public function setUp() { + if ( !wfDl( 'exif' ) ) { + $this->markTestSkipped( "This test needs the exif extension." ); } - - $file = UnregisteredLocalFile::newFromPath( dirname( __FILE__ ) . - '/../../data/media/broken_exif_date.jpg', 'image/jpeg' ); + $filePath = dirname( __FILE__ ) . '/../../data/media'; + $this->backend = new FSFileBackend( array( + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', + 'containerPaths' => array( 'data' => $filePath ) + ) ); + $this->repo = new FSRepo( array( + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', + 'backend' => $this->backend + ) ); + global $wgShowEXIF; + $this->show = $wgShowEXIF; + $wgShowEXIF = true; + } + public function tearDown() { + global $wgShowEXIF; + $wgShowEXIF = $this->show; + } + + public function testInvalidDate() { + $file = $this->dataFile( 'broken_exif_date.jpg', 'image/jpeg' ); // Throws an error if bug hit $meta = $file->formatMetadata(); @@ -26,4 +44,9 @@ class FormatMetadataTest extends MediaWikiTestCase { $meta['visible'][$dateIndex]['value'], 'File with invalid date metadata (bug 29471)' ); } -}
\ No newline at end of file + + private function dataFile( $name, $type ) { + return new UnregisteredLocalFile( false, $this->repo, + "mwstore://localtesting/data/$name", $type ); + } +} diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php index 59b30441..47fc368b 100644 --- a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php @@ -63,6 +63,7 @@ class GIFMetadataExtractorTest extends MediaWikiTestCase { <?xpacket end='w'?> EOF; + $xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat return array( array( 'nonanimated.gif', array( diff --git a/tests/phpunit/includes/media/GIFTest.php b/tests/phpunit/includes/media/GIFTest.php index 42c25ca5..36658358 100644 --- a/tests/phpunit/includes/media/GIFTest.php +++ b/tests/phpunit/includes/media/GIFTest.php @@ -2,12 +2,22 @@ class GIFHandlerTest extends MediaWikiTestCase { public function setUp() { - $this->filePath = dirname( __FILE__ ) . '/../../data/media/'; + $this->filePath = dirname( __FILE__ ) . '/../../data/media'; + $this->backend = new FSFileBackend( array( + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', + 'containerPaths' => array( 'data' => $this->filePath ) + ) ); + $this->repo = new FSRepo( array( + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', + 'backend' => $this->backend + ) ); $this->handler = new GIFHandler(); } public function testInvalidFile() { - $res = $this->handler->getMetadata( null, $this->filePath . 'README' ); + $res = $this->handler->getMetadata( null, $this->filePath . '/README' ); $this->assertEquals( GIFHandler::BROKEN_FILE, $res ); } /** @@ -16,8 +26,7 @@ class GIFHandlerTest extends MediaWikiTestCase { * @dataProvider dataIsAnimated */ public function testIsAnimanted( $filename, $expected ) { - $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename, - 'image/gif' ); + $file = $this->dataFile( $filename, 'image/gif' ); $actual = $this->handler->isAnimatedImage( $file ); $this->assertEquals( $expected, $actual ); } @@ -34,8 +43,7 @@ class GIFHandlerTest extends MediaWikiTestCase { * @dataProvider dataGetImageArea */ public function testGetImageArea( $filename, $expected ) { - $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename, - 'image/gif' ); + $file = $this->dataFile( $filename, 'image/gif' ); $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() ); $this->assertEquals( $expected, $actual ); } @@ -71,15 +79,20 @@ class GIFHandlerTest extends MediaWikiTestCase { * @dataProvider dataGetMetadata */ public function testGetMetadata( $filename, $expected ) { - $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename, - 'image/gif' ); - $actual = $this->handler->getMetadata( $file, $this->filePath . $filename ); + $file = $this->dataFile( $filename, 'image/gif' ); + $actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" ); $this->assertEquals( unserialize( $expected ), unserialize( $actual ) ); } + public function dataGetMetadata() { return array( array( 'nonanimated.gif', 'a:4:{s:10:"frameCount";i:1;s:6:"looped";b:0;s:8:"duration";d:0.1000000000000000055511151231257827021181583404541015625;s:8:"metadata";a:2:{s:14:"GIFFileComment";a:1:{i:0;s:35:"GIF test file ⁕ Created with GIMP";}s:15:"_MW_GIF_VERSION";i:1;}}' ), array( 'animated-xmp.gif', 'a:4:{s:10:"frameCount";i:4;s:6:"looped";b:1;s:8:"duration";d:2.399999999999999911182158029987476766109466552734375;s:8:"metadata";a:5:{s:6:"Artist";s:7:"Bawolff";s:16:"ImageDescription";a:2:{s:9:"x-default";s:18:"A file to test GIF";s:5:"_type";s:4:"lang";}s:15:"SublocationDest";s:13:"The interwebs";s:14:"GIFFileComment";a:1:{i:0;s:16:"GIƒ·test·file";}s:15:"_MW_GIF_VERSION";i:1;}}' ), ); } + + private function dataFile( $name, $type ) { + return new UnregisteredLocalFile( false, $this->repo, + "mwstore://localtesting/data/$name", $type ); + } } diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php index 61fc9c81..f48382a4 100644 --- a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php @@ -1,5 +1,5 @@ <?php -/* +/** * @todo Could use a test of extended XMP segments. Hard to find programs that * create example files, and creating my own in vim propbably wouldn't * serve as a very good "test". (Adobe photoshop probably creates such files @@ -59,7 +59,7 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { public function testPSIRExtraction() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' ); $expected = '50686f746f73686f7020332e30003842494d04040000000000181c02190004746573741c02190003666f6f1c020000020004'; - $this->assertEquals( $expected, bin2hex( $res['PSIR'] ) ); + $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) ); } public function testXMPExtractionAltAppId() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' ); @@ -70,19 +70,19 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { public function testIPTCHashComparisionNoHash() { $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' ); - $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] ); + $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] ); $this->assertEquals( 'iptc-no-hash', $res ); } public function testIPTCHashComparisionBadHash() { $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' ); - $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] ); + $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] ); $this->assertEquals( 'iptc-bad-hash', $res ); } public function testIPTCHashComparisionGoodHash() { $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' ); - $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] ); + $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] ); $this->assertEquals( 'iptc-good-hash', $res ); } diff --git a/tests/phpunit/includes/media/JpegTest.php b/tests/phpunit/includes/media/JpegTest.php index 713a3410..ddabf5b8 100644 --- a/tests/phpunit/includes/media/JpegTest.php +++ b/tests/phpunit/includes/media/JpegTest.php @@ -3,22 +3,24 @@ class JpegTest extends MediaWikiTestCase { public function setUp() { $this->filePath = dirname( __FILE__ ) . '/../../data/media/'; + if ( !wfDl( 'exif' ) ) { + $this->markTestSkipped( "This test needs the exif extension." ); + } + global $wgShowEXIF; + $this->show = $wgShowEXIF; + $wgShowEXIF = true; + } + public function tearDown() { + global $wgShowEXIF; + $wgShowEXIF = $this->show; } public function testInvalidFile() { - global $wgShowEXIF; - if ( !$wgShowEXIF ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $jpeg = new JpegHandler; $res = $jpeg->getMetadata( null, $this->filePath . 'README' ); $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res ); } public function testJpegMetadataExtraction() { - global $wgShowEXIF; - if ( !$wgShowEXIF ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $h = new JpegHandler; $res = $h->getMetadata( null, $this->filePath . 'test.jpg' ); $expected = 'a:7:{s:16:"ImageDescription";s:9:"Test file";s:11:"XResolution";s:4:"72/1";s:11:"YResolution";s:4:"72/1";s:14:"ResolutionUnit";i:2;s:16:"YCbCrPositioning";i:1;s:15:"JPEGFileComment";a:1:{i:0;s:17:"Created with GIMP";}s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}'; diff --git a/tests/phpunit/includes/ImageFunctionsTest.php b/tests/phpunit/includes/media/MediaHandlerTest.php index cb7e67f3..99df4f80 100644 --- a/tests/phpunit/includes/ImageFunctionsTest.php +++ b/tests/phpunit/includes/media/MediaHandlerTest.php @@ -1,6 +1,6 @@ <?php -class ImageFunctionsTest extends MediaWikiTestCase { +class MediaHandlerTest extends MediaWikiTestCase { function testFitBoxWidth() { $vals = array( array( @@ -32,10 +32,12 @@ class ImageFunctionsTest extends MediaWikiTestCase { 17 => 4, 18 => 4 ) ) ); foreach ( $vals as $row ) { - extract( $row ); + $tests = $row['tests']; + $height = $row['height']; + $width = $row['width']; foreach ( $tests as $max => $expected ) { $y = round( $expected * $height / $width ); - $result = wfFitBoxWidth( $width, $height, $max ); + $result = MediaHandler::fitBoxWidth( $width, $height, $max ); $y2 = round( $result * $height / $width ); $this->assertEquals( $expected, $result, diff --git a/tests/phpunit/includes/media/PNGTest.php b/tests/phpunit/includes/media/PNGTest.php index b782918c..b6f911fd 100644 --- a/tests/phpunit/includes/media/PNGTest.php +++ b/tests/phpunit/includes/media/PNGTest.php @@ -2,12 +2,22 @@ class PNGHandlerTest extends MediaWikiTestCase { public function setUp() { - $this->filePath = dirname( __FILE__ ) . '/../../data/media/'; + $this->filePath = dirname( __FILE__ ) . '/../../data/media'; + $this->backend = new FSFileBackend( array( + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', + 'containerPaths' => array( 'data' => $this->filePath ) + ) ); + $this->repo = new FSRepo( array( + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', + 'backend' => $this->backend + ) ); $this->handler = new PNGHandler(); } public function testInvalidFile() { - $res = $this->handler->getMetadata( null, $this->filePath . 'README' ); + $res = $this->handler->getMetadata( null, $this->filePath . '/README' ); $this->assertEquals( PNGHandler::BROKEN_FILE, $res ); } /** @@ -16,8 +26,7 @@ class PNGHandlerTest extends MediaWikiTestCase { * @dataProvider dataIsAnimated */ public function testIsAnimanted( $filename, $expected ) { - $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename, - 'image/png' ); + $file = $this->dataFile( $filename, 'image/png' ); $actual = $this->handler->isAnimatedImage( $file ); $this->assertEquals( $expected, $actual ); } @@ -34,8 +43,7 @@ class PNGHandlerTest extends MediaWikiTestCase { * @dataProvider dataGetImageArea */ public function testGetImageArea( $filename, $expected ) { - $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename, - 'image/png' ); + $file = $this->dataFile($filename, 'image/png' ); $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() ); $this->assertEquals( $expected, $actual ); } @@ -73,9 +81,8 @@ class PNGHandlerTest extends MediaWikiTestCase { * @dataProvider dataGetMetadata */ public function testGetMetadata( $filename, $expected ) { - $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename, - 'image/png' ); - $actual = $this->handler->getMetadata( $file, $this->filePath . $filename ); + $file = $this->dataFile( $filename, 'image/png' ); + $actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" ); // $this->assertEquals( unserialize( $expected ), unserialize( $actual ) ); $this->assertEquals( ( $expected ), ( $actual ) ); } @@ -85,4 +92,9 @@ class PNGHandlerTest extends MediaWikiTestCase { array( 'xmp.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:1;s:9:"colorType";s:14:"index-coloured";s:8:"metadata";a:2:{s:12:"SerialNumber";s:9:"123456789";s:15:"_MW_PNG_VERSION";i:1;}}' ), ); } + + private function dataFile( $name, $type ) { + return new UnregisteredLocalFile( false, $this->repo, + "mwstore://localtesting/data/$name", $type ); + } } diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php index c2c81b98..526beae8 100644 --- a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php @@ -62,23 +62,33 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase { 'height' => 60 ) ), + array( + "$base/Toll_Texas_1.svg", + // This file triggered bug 31719, needs entity expansion in the xmlns checks + array( + 'width' => 385, + 'height' => 385 + ) + ) ); } function providerSvgFilesWithXMLMetadata() { $base = dirname( __FILE__ ) . '/../../data/media'; - return array( - array( - "$base/US_states_by_total_state_tax_revenue.svg", - array( - 'height' => 593, - 'metadata' => + $metadata = '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <ns4:Work xmlns:ns4="http://creativecommons.org/ns#" rdf:about=""> <ns5:format xmlns:ns5="http://purl.org/dc/elements/1.1/">image/svg+xml</ns5:format> <ns5:type xmlns:ns5="http://purl.org/dc/elements/1.1/" rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> </ns4:Work> - </rdf:RDF>', + </rdf:RDF>'; + $metadata = str_replace( "\r", '', $metadata ); // Windows compat + return array( + array( + "$base/US_states_by_total_state_tax_revenue.svg", + array( + 'height' => 593, + 'metadata' => $metadata, 'width' => 959 ) ), diff --git a/tests/phpunit/includes/media/TiffTest.php b/tests/phpunit/includes/media/TiffTest.php index 0a7e8e8c..d4cf503b 100644 --- a/tests/phpunit/includes/media/TiffTest.php +++ b/tests/phpunit/includes/media/TiffTest.php @@ -15,16 +15,15 @@ class TiffTest extends MediaWikiTestCase { } public function testInvalidFile() { - global $wgShowEXIF; - if ( !$wgShowEXIF ) { + if ( !wfDl( 'exif' ) ) { $this->markTestIncomplete( "This test needs the exif extension." ); } $res = $this->handler->getMetadata( null, $this->filePath . 'README' ); $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res ); } + public function testTiffMetadataExtraction() { - global $wgShowEXIF; - if ( !$wgShowEXIF ) { + if ( !wfDl( 'exif' ) ) { $this->markTestIncomplete( "This test needs the exif extension." ); } $res = $this->handler->getMetadata( null, $this->filePath . 'test.tiff' ); diff --git a/tests/phpunit/includes/media/XMPTest.php b/tests/phpunit/includes/media/XMPTest.php index d1ec4767..c952b23c 100644 --- a/tests/phpunit/includes/media/XMPTest.php +++ b/tests/phpunit/includes/media/XMPTest.php @@ -1,6 +1,12 @@ <?php class XMPTest extends MediaWikiTestCase { + function setUp() { + if ( !wfDl( 'xml' ) ) { + $this->markTestSkipped( 'Requires libxml to do XMP parsing' ); + } + } + /** * Put XMP in, compare what comes out... * @@ -11,9 +17,6 @@ class XMPTest extends MediaWikiTestCase { * @dataProvider dataXMPParse */ public function testXMPParse( $xmp, $expected, $info ) { - if ( !function_exists( 'xml_parser_create_ns' ) ) { - $this->markIncomplete( 'Requires libxml to do XMP parsing' ); - } if ( !is_string( $xmp ) || !is_array( $expected ) ) { throw new Exception( "Invalid data provided to " . __METHOD__ ); } diff --git a/tests/phpunit/includes/media/XMPValidateTest.php b/tests/phpunit/includes/media/XMPValidateTest.php new file mode 100644 index 00000000..e2bb8d8d --- /dev/null +++ b/tests/phpunit/includes/media/XMPValidateTest.php @@ -0,0 +1,47 @@ +<?php +class XMPValidateTest extends MediaWikiTestCase { + + /** + * @dataProvider providerDate + */ + function testValidateDate( $value, $expected ) { + // The method should modify $value. + XMPValidate::validateDate( array(), $value, true ); + $this->assertEquals( $expected, $value ); + } + + function providerDate() { + /* For reference valid date formats are: + * YYYY + * YYYY-MM + * YYYY-MM-DD + * YYYY-MM-DDThh:mmTZD + * YYYY-MM-DDThh:mm:ssTZD + * YYYY-MM-DDThh:mm:ss.sTZD + * (Time zone is optional) + */ + return array( + array( '1992', '1992' ), + array( '1992-04', '1992:04' ), + array( '1992-02-01', '1992:02:01' ), + array( '2011-09-29', '2011:09:29' ), + array( '1982-12-15T20:12', '1982:12:15 20:12' ), + array( '1982-12-15T20:12Z', '1982:12:15 20:12' ), + array( '1982-12-15T20:12+02:30', '1982:12:15 22:42' ), + array( '1982-12-15T01:12-02:30', '1982:12:14 22:42' ), + array( '1982-12-15T20:12:11', '1982:12:15 20:12:11' ), + array( '1982-12-15T20:12:11Z', '1982:12:15 20:12:11' ), + array( '1982-12-15T20:12:11+01:10', '1982:12:15 21:22:11' ), + array( '2045-12-15T20:12:11', '2045:12:15 20:12:11' ), + array( '1867-06-01T15:00:00', '1867:06:01 15:00:00' ), + /* some invalid ones */ + array( '2001--12', null ), + array( '2001-5-12', null ), + array( '2001-5-12TZ', null ), + array( '2001-05-12T15', null ), + array( '2001-12T15:13', null ), + ); + + } + +} diff --git a/tests/phpunit/includes/parser/MagicVariableTest.php b/tests/phpunit/includes/parser/MagicVariableTest.php index a47653e3..31645313 100644 --- a/tests/phpunit/includes/parser/MagicVariableTest.php +++ b/tests/phpunit/includes/parser/MagicVariableTest.php @@ -6,8 +6,8 @@ * As of february 2011, it only tests some revisions and date related * magic variables. * - * @author Ashar Voultoiz - * @copyright Copyright © 2011, Ashar Voultoiz + * @author Antoine Musso + * @copyright Copyright © 2011, Antoine Musso * @file */ @@ -38,6 +38,12 @@ class MagicVariableTest extends MediaWikiTestCase { # initialize parser output $this->testParser->clearState(); + + # Needs a title to do magic word stuff + $title = Title::newFromText( 'Tests' ); + $title->mRedirect = false; # Else it needs a db connection just to check if it's a redirect (when deciding the page language) + + $this->testParser->setTitle( $title ); } /** destroy parser (TODO: is it really neded?)*/ diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php index 18510d9a..816c017a 100644 --- a/tests/phpunit/includes/parser/MediaWikiParserTest.php +++ b/tests/phpunit/includes/parser/MediaWikiParserTest.php @@ -1,10 +1,8 @@ <?php - -require_once( dirname( __FILE__ ) . '/ParserHelpers.php' ); require_once( dirname( __FILE__ ) . '/NewParserTest.php' ); /** - * The UnitTest must be either a class that inherits from PHPUnit_Framework_TestCase + * The UnitTest must be either a class that inherits from MediaWikiTestCase * or a class that provides a public static suite() method which returns * an PHPUnit_Framework_Test object * @@ -20,9 +18,13 @@ class MediaWikiParserTest { foreach ( $wgParserTestFiles as $filename ) { $testsName = basename( $filename, '.txt' ); - $className = /*ucfirst( basename( dirname( $filename ) ) ) .*/ ucfirst( basename( $filename, '.txt' ) ); + /* This used to be ucfirst( basename( dirname( $filename ) ) ) + * and then was ucfirst( basename( $filename, '.txt' ) + * but that didn't work with names like foo.tests.txt + */ + $className = str_replace( '.', '_', ucfirst( basename( $filename, '.txt' ) ) ); - eval( "/** @group Database\n@group Parser\n*/ class $className extends NewParserTest { protected \$file = \"" . addslashes( $filename ) . "\"; } " ); + eval( "/** @group Database\n@group Parser\n*/ class $className extends NewParserTest { protected \$file = '" . strtr( $filename, array( "'" => "\\'", '\\' => '\\\\' ) ) . "'; } " ); $parserTester = new $className( $testsName ); $suite->addTestSuite( new ReflectionClass ( $parserTester ) ); diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php index f4d5f757..d1221ca8 100644 --- a/tests/phpunit/includes/parser/NewParserTest.php +++ b/tests/phpunit/includes/parser/NewParserTest.php @@ -8,13 +8,12 @@ * @group Stub */ class NewParserTest extends MediaWikiTestCase { - static protected $articles = array(); // Array of test articles defined by the tests /* The dataProvider is run on a different instance than the test, so it must be static * When running tests from several files, all tests will see all articles. */ - - public $uploadDir; + static protected $backendToUse; + public $keepUploads = false; public $runDisabled = false; public $regex = ''; @@ -31,16 +30,12 @@ class NewParserTest extends MediaWikiTestCase { public $memoryLimit = 50; protected $file = false; - - /*function __construct($a = null,$b = array(),$c = null ) { - parent::__construct($a,$b,$c); - }*/ - + function setUp() { global $wgContLang, $wgNamespaceProtection, $wgNamespaceAliases; global $wgHooks, $IP; $wgContLang = Language::factory( 'en' ); - + //Setup CLI arguments if ( $this->getCliArg( 'regex=' ) ) { $this->regex = $this->getCliArg( 'regex=' ); @@ -48,11 +43,11 @@ class NewParserTest extends MediaWikiTestCase { # Matches anything $this->regex = ''; } - + $this->keepUploads = $this->getCliArg( 'keep-uploads' ); - + $tmpGlobals = array(); - + $tmpGlobals['wgScript'] = '/index.php'; $tmpGlobals['wgScriptPath'] = '/'; $tmpGlobals['wgArticlePath'] = '/wiki/$1'; @@ -60,15 +55,14 @@ class NewParserTest extends MediaWikiTestCase { $tmpGlobals['wgStylePath'] = '/skins'; $tmpGlobals['wgThumbnailScriptPath'] = false; $tmpGlobals['wgLocalFileRepo'] = array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'directory' => wfTempDir() . '/test-repo', - 'url' => 'http://example.com/images', - 'deletedDir' => wfTempDir() . '/test-repo/delete', - 'hashLevels' => 2, + 'class' => 'LocalRepo', + 'name' => 'local', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, 'transformVia404' => false, + 'backend' => 'local-backend' ); - + $tmpGlobals['wgForeignFileRepos'] = array(); $tmpGlobals['wgEnableParserCache'] = false; $tmpGlobals['wgHooks'] = $wgHooks; $tmpGlobals['wgDeferredUpdateList'] = array(); @@ -79,16 +73,16 @@ class NewParserTest extends MediaWikiTestCase { // $tmpGlobals['wgContLang'] = new StubContLang; $tmpGlobals['wgUser'] = new User; $context = new RequestContext(); - $tmpGlobals['wgLang'] = $context->getLang(); + $tmpGlobals['wgLang'] = $context->getLanguage(); $tmpGlobals['wgOut'] = $context->getOutput(); $tmpGlobals['wgParser'] = new StubObject( 'wgParser', $GLOBALS['wgParserConf']['class'], array( $GLOBALS['wgParserConf'] ) ); - $tmpGlobals['wgRequest'] = new WebRequest; + $tmpGlobals['wgRequest'] = $context->getRequest(); if ( $GLOBALS['wgStyleDirectory'] === false ) { $tmpGlobals['wgStyleDirectory'] = "$IP/skins"; } - - + + foreach ( $tmpGlobals as $var => $val ) { if ( array_key_exists( $var, $GLOBALS ) ) { $this->savedInitialGlobals[$var] = $GLOBALS[$var]; @@ -96,31 +90,38 @@ class NewParserTest extends MediaWikiTestCase { $GLOBALS[$var] = $val; } - + $this->savedWeirdGlobals['mw_namespace_protection'] = $wgNamespaceProtection[NS_MEDIAWIKI]; $this->savedWeirdGlobals['image_alias'] = $wgNamespaceAliases['Image']; $this->savedWeirdGlobals['image_talk_alias'] = $wgNamespaceAliases['Image_talk']; - + $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface'; $wgNamespaceAliases['Image'] = NS_FILE; $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; - } - + public function tearDown() { - foreach ( $this->savedInitialGlobals as $var => $val ) { $GLOBALS[$var] = $val; } - + global $wgNamespaceProtection, $wgNamespaceAliases; - + $wgNamespaceProtection[NS_MEDIAWIKI] = $this->savedWeirdGlobals['mw_namespace_protection']; $wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias']; $wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias']; + + // Restore backends + RepoGroup::destroySingleton(); + FileBackendGroup::destroySingleton(); } - + function addDBData() { + $this->tablesUsed[] = 'site_stats'; + $this->tablesUsed[] = 'interwiki'; + # disabled for performance + #$this->tablesUsed[] = 'image'; + # Hack: insert a few Wikipedia in-project interwiki prefixes, # for testing inter-language links $this->db->insert( 'interwiki', array( @@ -158,17 +159,14 @@ class NewParserTest extends MediaWikiTestCase { * @todo Fixme! Why are we inserting duplicate data here? Shouldn't * need this IGNORE or shouldn't need the insert at all. */ - ), __METHOD__, array( 'IGNORE' ) ); + ), __METHOD__, array( 'IGNORE' ) + ); # Update certain things in site_stats - $this->db->insert( 'site_stats', + $this->db->insert( 'site_stats', array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ), - __METHOD__, - /** - * @todo Fixme! Same as above! - */ - array( 'IGNORE' ) + __METHOD__ ); # Reinitialise the LocalisationCache to match the database state @@ -177,50 +175,66 @@ class NewParserTest extends MediaWikiTestCase { # Clear the message cache MessageCache::singleton()->clear(); - $this->uploadDir = $this->setupUploadDir(); - $user = User::newFromId( 0 ); LinkCache::singleton()->clear(); # Avoids the odd failure at creating the nullRevision - + + # Upload DB table entries for files. + # We will upload the actual files later. Note that if anything causes LocalFile::load() + # to be triggered before then, it will break via maybeUpgrade() setting the fileExists + # member to false and storing it in cache. $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) ); - $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', array( - 'size' => 12345, - 'width' => 1941, - 'height' => 220, - 'bits' => 24, - 'media_type' => MEDIATYPE_BITMAP, - 'mime' => 'image/jpeg', - 'metadata' => serialize( array() ), - 'sha1' => wfBaseConvert( '', 16, 36, 31 ), - 'fileExists' => true - ), $this->db->timestamp( '20010115123500' ), $user ); + if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { + $image->recordUpload2( + '', // archive name + 'Upload of some lame file', + 'Some lame file', + array( + 'size' => 12345, + 'width' => 1941, + 'height' => 220, + 'bits' => 24, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '', 16, 36, 31 ), + 'fileExists' => true ), + $this->db->timestamp( '20010115123500' ), $user + ); + } # This image will be blacklisted in [[MediaWiki:Bad image list]] $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) ); - $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', array( - 'size' => 12345, - 'width' => 320, - 'height' => 240, - 'bits' => 24, - 'media_type' => MEDIATYPE_BITMAP, - 'mime' => 'image/jpeg', - 'metadata' => serialize( array() ), - 'sha1' => wfBaseConvert( '', 16, 36, 31 ), - 'fileExists' => true - ), $this->db->timestamp( '20010115123500' ), $user ); - + if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { + $image->recordUpload2( + '', // archive name + 'zomgnotcensored', + 'Borderline image', + array( + 'size' => 12345, + 'width' => 320, + 'height' => 240, + 'bits' => 24, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '', 16, 36, 31 ), + 'fileExists' => true ), + $this->db->timestamp( '20010115123500' ), $user + ); + } } - - - - + + + + //ParserTest setup/teardown functions - + /** * Set up the global variables for a consistent environment for each test. * Ideally this should replace the global configuration entirely. */ protected function setupGlobals( $opts = '', $config = '' ) { + global $wgFileBackends; # Find out values for some special options. $lang = self::getOptionValue( 'language', $opts, 'en' ); @@ -231,19 +245,48 @@ class NewParserTest extends MediaWikiTestCase { $linkHolderBatchSize = self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 ); + $uploadDir = $this->getUploadDir(); + if ( $this->getCliArg( 'use-filebackend=' ) ) { + if ( self::$backendToUse ) { + $backend = self::$backendToUse; + } else { + $name = $this->getCliArg( 'use-filebackend=' ); + $useConfig = array(); + foreach ( $wgFileBackends as $conf ) { + if ( $conf['name'] == $name ) { + $useConfig = $conf; + } + } + $useConfig['name'] = 'local-backend'; // swap name + $class = $conf['class']; + self::$backendToUse = new $class( $useConfig ); + $backend = self::$backendToUse; + } + } else { + $backend = new FSFileBackend( array( + 'name' => 'local-backend', + 'lockManager' => 'nullLockManager', + 'containerPaths' => array( + 'local-public' => "$uploadDir", + 'local-thumb' => "$uploadDir/thumb", + ) + ) ); + } + $settings = array( 'wgServer' => 'http://Britney-Spears', 'wgScript' => '/index.php', 'wgScriptPath' => '/', 'wgArticlePath' => '/wiki/$1', + 'wgExtensionAssetsPath' => '/extensions', 'wgActionPaths' => array(), 'wgLocalFileRepo' => array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'directory' => $this->uploadDir, - 'url' => 'http://example.com/images', - 'hashLevels' => 2, + 'class' => 'LocalRepo', + 'name' => 'local', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, 'transformVia404' => false, + 'backend' => $backend ), 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ), 'wgStylePath' => '/skins', @@ -262,7 +305,7 @@ class NewParserTest extends MediaWikiTestCase { 'wgThumbnailScriptPath' => false, 'wgUseImageResize' => false, 'wgUseTeX' => isset( $opts['math'] ), - 'wgMathDirectory' => $this->uploadDir . '/math', + 'wgMathDirectory' => $uploadDir . '/math', 'wgLocaltimezone' => 'UTC', 'wgAllowExternalImages' => true, 'wgUseTidy' => false, @@ -283,6 +326,7 @@ class NewParserTest extends MediaWikiTestCase { 'wgExternalLinkTarget' => false, 'wgAlwaysUseTidy' => false, 'wgHtml5' => true, + 'wgCleanupPresentationalAttributes' => true, 'wgWellFormedXml' => true, 'wgAllowMicrodataAttributes' => true, 'wgAdaptiveMessageCache' => true, @@ -312,39 +356,41 @@ class NewParserTest extends MediaWikiTestCase { $langObj = Language::factory( $lang ); $GLOBALS['wgContLang'] = $langObj; $context = new RequestContext(); - $GLOBALS['wgLang'] = $context->getLang(); + $GLOBALS['wgLang'] = $context->getLanguage(); $GLOBALS['wgMemc'] = new EmptyBagOStuff; $GLOBALS['wgOut'] = $context->getOutput(); + $GLOBALS['wgUser'] = $context->getUser(); global $wgHooks; $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup'; - $wgHooks['ParserTestParser'][] = 'ParserTestStaticParserHook::setup'; $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; MagicWord::clearCache(); + RepoGroup::destroySingleton(); + FileBackendGroup::destroySingleton(); + + # Create dummy files in storage + $this->setupUploads(); # Publish the articles after we have the final language set $this->publishTestArticles(); # The entries saved into RepoGroup cache with previous globals will be wrong. RepoGroup::destroySingleton(); + FileBackendGroup::destroySingleton(); MessageCache::singleton()->destroyInstance(); - - global $wgUser; - $wgUser = new User(); + + return $context; } /** - * Create a dummy uploads directory which will contain a couple - * of files in order to pass existence tests. + * Get an FS upload directory (only applies to FSFileBackend) * * @return String: the directory */ - protected function setupUploadDir() { - global $IP; - + protected function getUploadDir() { if ( $this->keepUploads ) { $dir = wfTempDir() . '/mwParser-images'; @@ -361,70 +407,67 @@ class NewParserTest extends MediaWikiTestCase { return $dir; } - wfMkdirParents( $dir . '/3/3a' ); - copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" ); - wfMkdirParents( $dir . '/0/09' ); - copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" ); - return $dir; } - + + /** + * Create a dummy uploads directory which will contain a couple + * of files in order to pass existence tests. + * + * @return String: the directory + */ + protected function setupUploads() { + global $IP; + + $base = $this->getBaseDir(); + $backend = RepoGroup::singleton()->getLocalRepo()->getBackend(); + $backend->prepare( array( 'dir' => "$base/local-public/3/3a" ) ); + $backend->store( array( + 'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/3/3a/Foobar.jpg" + ) ); + $backend->prepare( array( 'dir' => "$base/local-public/0/09" ) ); + $backend->store( array( + 'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/0/09/Bad.jpg" + ) ); + } + /** * Restore default values and perform any necessary clean-up * after each test runs. */ protected function teardownGlobals() { - RepoGroup::destroySingleton(); - LinkCache::singleton()->clear(); + $this->teardownUploads(); foreach ( $this->savedGlobals as $var => $val ) { $GLOBALS[$var] = $val; } - - $this->teardownUploadDir( $this->uploadDir ); + + RepoGroup::destroySingleton(); + LinkCache::singleton()->clear(); } /** * Remove the dummy uploads directory */ - private function teardownUploadDir( $dir ) { + private function teardownUploads() { if ( $this->keepUploads ) { return; } + $base = $this->getBaseDir(); // delete the files first, then the dirs. self::deleteFiles( array ( - "$dir/3/3a/Foobar.jpg", - "$dir/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg", - "$dir/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg", - "$dir/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg", - "$dir/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg", - - "$dir/0/09/Bad.jpg", + "$base/local-public/3/3a/Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/180px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/200px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/640px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/120px-Foobar.jpg", - "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png", - ) - ); + "$base/local-public/0/09/Bad.jpg", + "$base/local-thumb/0/09/Bad.jpg", - self::deleteDirs( - array ( - "$dir/3/3a", - "$dir/3", - "$dir/thumb/6/65", - "$dir/thumb/6", - "$dir/thumb/3/3a/Foobar.jpg", - "$dir/thumb/3/3a", - "$dir/thumb/3", - - "$dir/0/09/", - "$dir/0/", - "$dir/thumb", - "$dir/math/f/a/5", - "$dir/math/f/a", - "$dir/math/f", - "$dir/math", - "$dir", + "$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png", ) ); } @@ -434,25 +477,24 @@ class NewParserTest extends MediaWikiTestCase { * @param $files Array: full paths to files to delete. */ private static function deleteFiles( $files ) { + $backend = RepoGroup::singleton()->getLocalRepo()->getBackend(); foreach ( $files as $file ) { - if ( file_exists( $file ) ) { - unlink( $file ); + $backend->delete( array( 'src' => $file ), array( 'force' => 1 ) ); + } + foreach ( $files as $file ) { + $tmp = $file; + while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) { + if ( !$backend->clean( array( 'dir' => $tmp ) )->isOK() ) { + break; + } } } } - /** - * Delete the specified directories, if they exist. Must be empty. - * @param $dirs Array: full paths to directories to delete. - */ - private static function deleteDirs( $dirs ) { - foreach ( $dirs as $dir ) { - if ( is_dir( $dir ) ) { - rmdir( $dir ); - } - } + protected function getBaseDir() { + return 'mwstore://local-backend'; } - + public function parserTestProvider() { if ( $this->file === false ) { global $wgParserTestFiles; @@ -460,25 +502,29 @@ class NewParserTest extends MediaWikiTestCase { } return new TestFileIterator( $this->file, $this ); } - + /** * Set the file from whose tests will be run by this instance */ public function setParserTestFile( $filename ) { $this->file = $filename; } - + /** @dataProvider parserTestProvider */ public function testParserTest( $desc, $input, $result, $opts, $config ) { - if ( !preg_match( '/' . $this->regex . '/', $desc ) ) return; //$this->markTestSkipped( 'Filtered out by the user' ); + if ( $this->regex != '' && !preg_match( '/' . $this->regex . '/', $desc ) ) { + $this->assertTrue( true ); // XXX: don't flood output with "test made no assertions" + //$this->markTestSkipped( 'Filtered out by the user' ); + return; + } wfDebug( "Running parser test: $desc\n" ); $opts = $this->parseOptions( $opts ); - $this->setupGlobals( $opts, $config ); + $context = $this->setupGlobals( $opts, $config ); - $user = new User(); - $options = ParserOptions::newFromUser( $user ); + $user = $context->getUser(); + $options = ParserOptions::newFromContext( $context ); if ( isset( $opts['title'] ) ) { $titleText = $opts['title']; @@ -490,7 +536,7 @@ class NewParserTest extends MediaWikiTestCase { $local = isset( $opts['local'] ); $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null; $parser = $this->getParser( $preprocessor ); - + $title = Title::newFromText( $titleText ); if ( isset( $opts['pst'] ) ) { @@ -505,8 +551,7 @@ class NewParserTest extends MediaWikiTestCase { $replace = $opts['replace'][1]; $out = $parser->replaceSection( $input, $section, $replace ); } elseif ( isset( $opts['comment'] ) ) { - $linker = $user->getSkin(); - $out = $linker->formatComment( $input, $title, $local ); + $out = Linker::formatComment( $input, $title, $local ); } elseif ( isset( $opts['preload'] ) ) { $out = $parser->getpreloadText( $input, $title, $options ); } else { @@ -524,10 +569,9 @@ class NewParserTest extends MediaWikiTestCase { if ( isset( $opts['ill'] ) ) { $out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) ); } elseif ( isset( $opts['cat'] ) ) { - global $wgOut; - - $wgOut->addCategoryLinks( $output->getCategories() ); - $cats = $wgOut->getCategoryLinks(); + $outputPage = $context->getOutput(); + $outputPage->addCategoryLinks( $output->getCategories() ); + $cats = $outputPage->getCategoryLinks(); if ( isset( $cats['normal'] ) ) { $out = $this->tidy( implode( ' ', $cats['normal'] ) ); @@ -541,38 +585,41 @@ class NewParserTest extends MediaWikiTestCase { } $this->teardownGlobals(); - + $this->assertEquals( $result, $out, $desc ); } - + /** * Run a fuzz test series * Draw input from a set of test files + * + * @todo @fixme Needs some work to not eat memory until the world explodes + * + * @group ParserFuzz */ function testFuzzTests() { - - $this->markTestIncomplete( "Somebody is serializing PDO objects, that's a no-no" ); - global $wgParserTestFiles; - + $files = $wgParserTestFiles; - + if( $this->getCliArg( 'file=' ) ) { $files = array( $this->getCliArg( 'file=' ) ); } - + $dict = $this->getFuzzInput( $files ); $dictSize = strlen( $dict ); $logMaxLength = log( $this->maxFuzzTestLength ); + ini_set( 'memory_limit', $this->memoryLimit * 1048576 ); + $user = new User; $opts = ParserOptions::newFromUser( $user ); $title = Title::makeTitle( NS_MAIN, 'Parser_test' ); $id = 1; - + while ( true ) { - + // Generate test input mt_srand( ++$this->fuzzSeed ); $totalLength = mt_rand( 1, $this->maxFuzzTestLength ); @@ -594,7 +641,7 @@ class NewParserTest extends MediaWikiTestCase { $this->assertTrue( true, "Test $id, fuzz seed {$this->fuzzSeed}" ); } catch ( Exception $exception ) { $input_dump = sprintf( "string(%d) \"%s\"\n", strlen( $input ), $input ); - + $this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\nInput: $input_dump\n\nError: {$exception->getMessage()}\n\nBacktrace: {$exception->getTraceAsString()}" ); } @@ -611,18 +658,18 @@ class NewParserTest extends MediaWikiTestCase { foreach ( $memStats as $name => $usage ) { $ret .= "$name: $usage\n"; } - + throw new MWException( $ret ); } } - + $id++; - + } } //Various getter functions - + /** * Get an input dictionary from a set of parser test files */ @@ -640,7 +687,7 @@ class NewParserTest extends MediaWikiTestCase { return $dict; } - + /** * Get a memory usage breakdown */ @@ -675,7 +722,7 @@ class NewParserTest extends MediaWikiTestCase { return $memStats; } - + /** * Get a Parser object */ @@ -693,23 +740,20 @@ class NewParserTest extends MediaWikiTestCase { //Various action functions public function addArticle( $name, $text, $line ) { - self::$articles[$name] = $text; - } - + self::$articles[$name] = array( $text, $line ); + } + public function publishTestArticles() { if ( empty( self::$articles ) ) { return; } - foreach ( self::$articles as $name => $text ) { - $title = Title::newFromText( $name ); - - if ( $title->getArticleID( Title::GAID_FOR_UPDATE ) == 0 ) { - ParserTest::addArticle( $name, $text ); - } + foreach ( self::$articles as $name => $info ) { + list( $text, $line ) = $info; + ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' ); } } - + /** * Steal a callback function from the primary parser, save it for * application to our scary parser. If the hook is not installed, @@ -730,8 +774,8 @@ class NewParserTest extends MediaWikiTestCase { return isset( $wgParser->mFunctionHooks[$name] ); } //Various "cleanup" functions - - /* + + /** * Run the "tidy" command on text if the $wgUseTidy * global is true * @@ -747,7 +791,7 @@ class NewParserTest extends MediaWikiTestCase { return $text; } - + /** * Remove last character if it is a newline */ @@ -760,12 +804,8 @@ class NewParserTest extends MediaWikiTestCase { } } - public function showRunFile( $file ) { - /* NOP */ - } - //Test options parser functions - + protected function parseOptions( $instring ) { $opts = array(); // foo @@ -820,7 +860,7 @@ class NewParserTest extends MediaWikiTestCase { } return $opts; } - + protected function cleanupOption( $opt ) { if ( substr( $opt, 0, 1 ) == '"' ) { return substr( $opt, 1, -1 ); @@ -831,7 +871,7 @@ class NewParserTest extends MediaWikiTestCase { } return $opt; } - + /** * Use a regex to find out the value of an option * @param $key String: name of option val to retrieve diff --git a/tests/phpunit/includes/parser/ParserHelpers.php b/tests/phpunit/includes/parser/ParserHelpers.php deleted file mode 100644 index 4a6ce7c4..00000000 --- a/tests/phpunit/includes/parser/ParserHelpers.php +++ /dev/null @@ -1,136 +0,0 @@ -<?php - -class PHPUnitParserTest extends ParserTest { - function showTesting( $desc ) { - /* Do nothing since we don't want to show info during PHPUnit testing. */ - } - - public function showSuccess( $desc ) { - PHPUnit_Framework_Assert::assertTrue( true, $desc ); - return true; - } - - public function showFailure( $desc, $expected, $got ) { - PHPUnit_Framework_Assert::assertEquals( $expected, $got, $desc ); - return false; - } - - public function setupRecorder( $options ) { - $this->recorder = new PHPUnitTestRecorder( $this ); - } -} - -class ParserUnitTest extends MediaWikiTestCase { - private $test = ""; - - public function __construct( $suite, $test = null ) { - parent::__construct(); - $this->test = $test; - $this->suite = $suite; - } - - function count() { return 1; } - - public function run( PHPUnit_Framework_TestResult $result = null ) { - PHPUnit_Framework_Assert::resetCount(); - if ( $result === NULL ) { - $result = new PHPUnit_Framework_TestResult; - } - - $this->suite->publishTestArticles(); // Add articles needed by the tests. - $backend = new ParserTestSuiteBackend; - $result->startTest( $this ); - - // Support the transition to PHPUnit 3.5 where PHPUnit_Util_Timer is replaced with PHP_Timer - if ( class_exists( 'PHP_Timer' ) ) { - PHP_Timer::start(); - } else { - PHPUnit_Util_Timer::start(); - } - - $r = false; - try { - # Run the test. - # On failure, the subclassed backend will throw an exception with - # the details. - $pt = new PHPUnitParserTest; - $r = $pt->runTest( $this->test['test'], $this->test['input'], - $this->test['result'], $this->test['options'], $this->test['config'] - ); - } - catch ( PHPUnit_Framework_AssertionFailedError $e ) { - - // PHPUnit_Util_Timer -> PHP_Timer support (see above) - if ( class_exists( 'PHP_Timer' ) ) { - $result->addFailure( $this, $e, PHP_Timer::stop() ); - } else { - $result->addFailure( $this, $e, PHPUnit_Util_Timer::stop() ); - } - } - catch ( Exception $e ) { - // PHPUnit_Util_Timer -> PHP_Timer support (see above) - if ( class_exists( 'PHP_Timer' ) ) { - $result->addFailure( $this, $e, PHP_Timer::stop() ); - } else { - $result->addFailure( $this, $e, PHPUnit_Util_Timer::stop() ); - } - } - - // PHPUnit_Util_Timer -> PHP_Timer support (see above) - if ( class_exists( 'PHP_Timer' ) ) { - $result->endTest( $this, PHP_Timer::stop() ); - } else { - $result->endTest( $this, PHPUnit_Util_Timer::stop() ); - } - - $backend->recorder->record( $this->test['test'], $r ); - $this->addToAssertionCount( PHPUnit_Framework_Assert::getCount() ); - - return $result; - } - - public function toString() { - return $this->test['test']; - } - -} - -class ParserTestSuiteBackend extends PHPUnit_FrameWork_TestSuite { - public $recorder; - public $term; - static $usePHPUnit = false; - - function __construct() { - parent::__construct(); - $this->setupRecorder(null); - self::$usePHPUnit = method_exists('PHPUnit_Framework_Assert', 'assertEquals'); - } - - function showTesting( $desc ) { - } - - function showRunFile( $path ) { - } - - function showTestResult( $desc, $result, $out ) { - if ( $result === $out ) { - return self::showSuccess( $desc, $result, $out ); - } else { - return self::showFailure( $desc, $result, $out ); - } - } - - public function setupRecorder( $options ) { - $this->recorder = new PHPUnitTestRecorder( $this ); - } -} - -class PHPUnitTestRecorder extends TestRecorder { - function record( $test, $result ) { - $this->total++; - $this->success += $result; - - } - - function reportPercentage( $success, $total ) { } -} diff --git a/tests/phpunit/includes/parser/ParserPreloadTest.php b/tests/phpunit/includes/parser/ParserPreloadTest.php new file mode 100644 index 00000000..0e8ef530 --- /dev/null +++ b/tests/phpunit/includes/parser/ParserPreloadTest.php @@ -0,0 +1,67 @@ +<?php +/** + * Basic tests for Parser::getPreloadText + * @author Antoine Musso + */ +class ParserPreloadTest extends MediaWikiTestCase { + private $testParser; + private $testParserOptions; + private $title; + + function setUp() { + $this->testParserOptions = new ParserOptions(); + + $this->testParser = new Parser(); + $this->testParser->Options( $this->testParserOptions ); + $this->testParser->clearState(); + + $this->title = Title::newFromText( 'Preload Test' ); + } + + function tearDown() { + unset( $this->testParser ); + unset( $this->title ); + } + + /** + * @covers Parser::getPreloadText + */ + function testPreloadSimpleText() { + $this->assertPreloaded( 'simple', 'simple' ); + } + + /** + * @covers Parser::getPreloadText + */ + function testPreloadedPreIsUnstripped() { + $this->assertPreloaded( + '<pre>monospaced</pre>', + '<pre>monospaced</pre>', + '<pre> in preloaded text must be unstripped (bug 27467)' + ); + } + + /** + * @covers Parser::getPreloadText + */ + function testPreloadedNowikiIsUnstripped() { + $this->assertPreloaded( + '<nowiki>[[Dummy title]]</nowiki>', + '<nowiki>[[Dummy title]]</nowiki>', + '<nowiki> in preloaded text must be unstripped (bug 27467)' + ); + } + + function assertPreloaded( $expected, $text, $msg='') { + $this->assertEquals( + $expected, + $this->testParser->getPreloadText( + $text, + $this->title, + $this->testParserOptions + ), + $msg + ); + } + +} diff --git a/tests/phpunit/includes/parser/PreprocessorTest.php b/tests/phpunit/includes/parser/PreprocessorTest.php index 7a5948d4..9d3499a0 100644 --- a/tests/phpunit/includes/parser/PreprocessorTest.php +++ b/tests/phpunit/includes/parser/PreprocessorTest.php @@ -49,43 +49,45 @@ class PreprocessorTest extends MediaWikiTestCase { array( "== Foo ==\n== Bar == \n", "<root><h level=\"2\" i=\"1\">== Foo ==</h>\n<h level=\"2\" i=\"2\">== Bar == </h>\n</root>" ), array( "===========", "<root><h level=\"5\" i=\"1\">===========</h></root>" ), array( "Foo\n=\n==\n=\n", "<root>Foo\n=\n==\n=\n</root>" ), - array( "{{Foo}}", "<root><template lineStart=\"1\"><title>Foo</title></template></root>" ), + array( "{{Foo}}", "<root><template><title>Foo</title></template></root>" ), array( "\n{{Foo}}", "<root>\n<template lineStart=\"1\"><title>Foo</title></template></root>" ), - array( "{{Foo|bar}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template></root>" ), - array( "{{Foo|bar}}a", "<root><template lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template>a</root>" ), - array( "{{Foo|bar|baz}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></template></root>" ), - array( "{{Foo|1=bar}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name>1</name>=<value>bar</value></part></template></root>" ), - array( "{{Foo|=bar}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name></name>=<value>bar</value></part></template></root>" ), - array( "{{Foo|bar=baz}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name>bar</name>=<value>baz</value></part></template></root>" ), - array( "{{Foo|1=bar|baz}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name>1</name>=<value>bar</value></part><part><name index=\"1\" /><value>baz</value></part></template></root>" ), - array( "{{Foo|1=bar|2=baz}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name>1</name>=<value>bar</value></part><part><name>2</name>=<value>baz</value></part></template></root>" ), - array( "{{Foo|bar|foo=baz}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name>foo</name>=<value>baz</value></part></template></root>" ), - array( "{{{1}}}", "<root><tplarg lineStart=\"1\"><title>1</title></tplarg></root>" ), - array( "{{{1|}}}", "<root><tplarg lineStart=\"1\"><title>1</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ), - array( "{{{Foo}}}", "<root><tplarg lineStart=\"1\"><title>Foo</title></tplarg></root>" ), - array( "{{{Foo|}}}", "<root><tplarg lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ), - array( "{{{Foo|bar|baz}}}", "<root><tplarg lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></tplarg></root>" ), + array( "{{Foo|bar}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template></root>" ), + array( "{{Foo|bar}}a", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template>a</root>" ), + array( "{{Foo|bar|baz}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></template></root>" ), + array( "{{Foo|1=bar}}", "<root><template><title>Foo</title><part><name>1</name>=<value>bar</value></part></template></root>" ), + array( "{{Foo|=bar}}", "<root><template><title>Foo</title><part><name></name>=<value>bar</value></part></template></root>" ), + array( "{{Foo|bar=baz}}", "<root><template><title>Foo</title><part><name>bar</name>=<value>baz</value></part></template></root>" ), + array( "{{Foo|{{bar}}=baz}}", "<root><template><title>Foo</title><part><name><template><title>bar</title></template></name>=<value>baz</value></part></template></root>" ), + array( "{{Foo|1=bar|baz}}", "<root><template><title>Foo</title><part><name>1</name>=<value>bar</value></part><part><name index=\"1\" /><value>baz</value></part></template></root>" ), + array( "{{Foo|1=bar|2=baz}}", "<root><template><title>Foo</title><part><name>1</name>=<value>bar</value></part><part><name>2</name>=<value>baz</value></part></template></root>" ), + array( "{{Foo|bar|foo=baz}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name>foo</name>=<value>baz</value></part></template></root>" ), + array( "{{{1}}}", "<root><tplarg><title>1</title></tplarg></root>" ), + array( "{{{1|}}}", "<root><tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ), + array( "{{{Foo}}}", "<root><tplarg><title>Foo</title></tplarg></root>" ), + array( "{{{Foo|}}}", "<root><tplarg><title>Foo</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ), + array( "{{{Foo|bar|baz}}}", "<root><tplarg><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></tplarg></root>" ), array( "{<!-- -->{Foo}}", "<root>{<comment><!-- --></comment>{Foo}}</root>" ), array( "{{{{Foobar}}}}", "<root>{<tplarg><title>Foobar</title></tplarg>}</root>" ), - array( "{{{ {{Foo}} }}}", "<root><tplarg lineStart=\"1\"><title> <template><title>Foo</title></template> </title></tplarg></root>" ), - array( "{{ {{{Foo}}} }}", "<root><template lineStart=\"1\"><title> <tplarg><title>Foo</title></tplarg> </title></template></root>" ), - array( "{{{{{Foo}}}}}", "<root><template lineStart=\"1\"><title><tplarg><title>Foo</title></tplarg></title></template></root>" ), - array( "{{{{{Foo}} }}}", "<root><tplarg lineStart=\"1\"><title><template><title>Foo</title></template> </title></tplarg></root>" ), - array( "{{{{{{Foo}}}}}}", "<root><tplarg lineStart=\"1\"><title><tplarg><title>Foo</title></tplarg></title></tplarg></root>" ), + array( "{{{ {{Foo}} }}}", "<root><tplarg><title> <template><title>Foo</title></template> </title></tplarg></root>" ), + array( "{{ {{{Foo}}} }}", "<root><template><title> <tplarg><title>Foo</title></tplarg> </title></template></root>" ), + array( "{{{{{Foo}}}}}", "<root><template><title><tplarg><title>Foo</title></tplarg></title></template></root>" ), + array( "{{{{{Foo}} }}}", "<root><tplarg><title><template><title>Foo</title></template> </title></tplarg></root>" ), + array( "{{{{{{Foo}}}}}}", "<root><tplarg><title><tplarg><title>Foo</title></tplarg></title></tplarg></root>" ), array( "{{{{{{Foo}}}}}", "<root>{<template><title><tplarg><title>Foo</title></tplarg></title></template></root>" ), array( "[[[Foo]]", "<root>[[[Foo]]</root>" ), - array( "{{Foo|[[[[bar]]|baz]]}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>[[[[bar]]|baz]]</value></part></template></root>" ), // This test is important, since it means the difference between having the [[ rule stacked or not + array( "{{Foo|[[[[bar]]|baz]]}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>[[[[bar]]|baz]]</value></part></template></root>" ), // This test is important, since it means the difference between having the [[ rule stacked or not array( "{{Foo|[[[[bar]|baz]]}}", "<root>{{Foo|[[[[bar]|baz]]}}</root>" ), array( "{{Foo|Foo [[[[bar]|baz]]}}", "<root>{{Foo|Foo [[[[bar]|baz]]}}</root>" ), array( "Foo <display map>Bar</display map >Baz", "<root>Foo <ext><name>display map</name><attr></attr><inner>Bar</inner><close></display map ></close></ext>Baz</root>" ), array( "Foo <display map foo>Bar</display map >Baz", "<root>Foo <ext><name>display map</name><attr> foo</attr><inner>Bar</inner><close></display map ></close></ext>Baz</root>" ), array( "Foo <gallery bar=\"baz\" />", "<root>Foo <ext><name>gallery</name><attr> bar="baz" </attr></ext></root>" ), + array( "Foo <gallery bar=\"1\" baz=2 />", "<root>Foo <ext><name>gallery</name><attr> bar="1" baz=2 </attr></ext></root>" ), array( "</foo>Foo<//foo>", "<root><ext><name>/foo</name><attr></attr><inner>Foo</inner><close><//foo></close></ext></root>" ), # Worth blacklisting IMHO - array( "{{#ifexpr: ({{{1|1}}} = 2) | Foo | Bar }}", "<root><template lineStart=\"1\"><title>#ifexpr: (<tplarg><title>1</title><part><name index=\"1\" /><value>1</value></part></tplarg> = 2) </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>"), - array( "{{#if: {{{1|}}} | Foo | {{Bar}} }}", "<root><template lineStart=\"1\"><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> <template><title>Bar</title></template> </value></part></template></root>"), - array( "{{#if: {{{1|}}} | Foo | [[Bar]] }}", "<root><template lineStart=\"1\"><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> [[Bar]] </value></part></template></root>"), - array( "{{#if: {{{1|}}} | [[Foo]] | Bar }}", "<root><template lineStart=\"1\"><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> [[Foo]] </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>"), - array( "{{#if: {{{1|}}} | 1 | {{#if: {{{1|}}} | 2 | 3 }} }}", "<root><template lineStart=\"1\"><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 1 </value></part><part><name index=\"2\" /><value> <template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 2 </value></part><part><name index=\"2\" /><value> 3 </value></part></template> </value></part></template></root>"), + array( "{{#ifexpr: ({{{1|1}}} = 2) | Foo | Bar }}", "<root><template><title>#ifexpr: (<tplarg><title>1</title><part><name index=\"1\" /><value>1</value></part></tplarg> = 2) </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>"), + array( "{{#if: {{{1|}}} | Foo | {{Bar}} }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> <template><title>Bar</title></template> </value></part></template></root>"), + array( "{{#if: {{{1|}}} | Foo | [[Bar]] }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> [[Bar]] </value></part></template></root>"), + array( "{{#if: {{{1|}}} | [[Foo]] | Bar }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> [[Foo]] </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>"), + array( "{{#if: {{{1|}}} | 1 | {{#if: {{{1|}}} | 2 | 3 }} }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 1 </value></part><part><name index=\"2\" /><value> <template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 2 </value></part><part><name index=\"2\" /><value> 3 </value></part></template> </value></part></template></root>"), array( "{{ {{Foo}}", "<root>{{ <template><title>Foo</title></template></root>"), array( "{{Foobar {{Foo}} {{Bar}} {{Baz}} ", "<root>{{Foobar <template><title>Foo</title></template> <template><title>Bar</title></template> <template><title>Baz</title></template> </root>"), array( "[[Foo]] |", "<root>[[Foo]] |</root>"), @@ -97,19 +99,54 @@ class PreprocessorTest extends MediaWikiTestCase { array( "{{Foo|bar=[[baz]}}", "<root>{{Foo|bar=[[baz]}}</root>"), array( "{{foo|", "<root>{{foo|</root>"), array( "{{foo|}", "<root>{{foo|}</root>"), - array( "{{foo|} }}", "<root><template lineStart=\"1\"><title>foo</title><part><name index=\"1\" /><value>} </value></part></template></root>"), + array( "{{foo|} }}", "<root><template><title>foo</title><part><name index=\"1\" /><value>} </value></part></template></root>"), array( "{{foo|bar=|}", "<root>{{foo|bar=|}</root>"), array( "{{Foo|} Bar=", "<root>{{Foo|} Bar=</root>"), - array( "{{Foo|} Bar=}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>"), + array( "{{Foo|} Bar=}}", "<root><template><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>"), /* array( file_get_contents( dirname( __FILE__ ) . '/QuoteQuran.txt' ), file_get_contents( dirname( __FILE__ ) . '/QuoteQuranExpanded.txt' ) ), */ ); } /** + * Get XML preprocessor tree from the preprocessor (which may not be the + * native XML-based one). + * + * @param string $wikiText + * @return string + */ + function preprocessToXml( $wikiText ) { + if ( method_exists( $this->mPreprocessor, 'preprocessToXml' ) ) { + return $this->normalizeXml( $this->mPreprocessor->preprocessToXml( $wikiText ) ); + } + + $dom = $this->mPreprocessor->preprocessToObj( $wikiText ); + if ( is_callable( array( $dom, 'saveXML' ) ) ) { + return $dom->saveXML(); + } else { + return $this->normalizeXml( $dom->__toString() ); + } + } + + /** + * Normalize XML string to the form that a DOMDocument saves out. + * + * @param string $xml + * @return string + */ + function normalizeXml( $xml ) { + return preg_replace( '!<([a-z]+)/>!', '<$1></$1>', str_replace( ' />', '/>', $xml ) ); + + $dom = new DOMDocument(); + // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 don't barf when the XML is >256 levels deep + $dom->loadXML( $xml, 1 << 19 ); + return $dom->saveXML(); + } + + /** * @dataProvider provideCases */ function testPreprocessorOutput( $wikiText, $expectedXml ) { - $this->assertEquals( $expectedXml, $this->mPreprocessor->preprocessToXml( $wikiText ) ); + $this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) ); } /** @@ -130,11 +167,12 @@ class PreprocessorTest extends MediaWikiTestCase { function testPreprocessorOutputFiles( $filename ) { $folder = dirname( __FILE__ ) . "/../../../parser/preprocess"; $wikiText = file_get_contents( "$folder/$filename.txt" ); - $output = $this->mPreprocessor->preprocessToXml( $wikiText ); + $output = $this->preprocessToXml( $wikiText ); $expectedFilename = "$folder/$filename.expected"; if ( file_exists( $expectedFilename ) ) { - $this->assertStringEqualsFile( $expectedFilename, $output ); + $expectedXml = $this->normalizeXml( file_get_contents( $expectedFilename ) ); + $this->assertEquals( $expectedXml, $output ); } else { $tempFilename = tempnam( $folder, "$filename." ); file_put_contents( $tempFilename, $output ); @@ -189,7 +227,7 @@ class PreprocessorTest extends MediaWikiTestCase { * @dataProvider provideHeadings */ function testHeadings( $wikiText, $expectedXml ) { - $this->assertEquals( $expectedXml, $this->mPreprocessor->preprocessToXml( $wikiText ) ); + $this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) ); } } diff --git a/tests/phpunit/includes/parser/TagHooks.php b/tests/phpunit/includes/parser/TagHooksTest.php index 713ce846..713ce846 100644 --- a/tests/phpunit/includes/parser/TagHooks.php +++ b/tests/phpunit/includes/parser/TagHooksTest.php diff --git a/tests/phpunit/includes/search/SearchEngineTest.php b/tests/phpunit/includes/search/SearchEngineTest.php index 1a0fcd31..957907c7 100644 --- a/tests/phpunit/includes/search/SearchEngineTest.php +++ b/tests/phpunit/includes/search/SearchEngineTest.php @@ -12,7 +12,7 @@ class SearchEngineTest extends MediaWikiTestCase { unset( $this->search ); } - /* + /** * Checks for database type & version. * Will skip current test if DB does not support search. */ @@ -64,8 +64,10 @@ class SearchEngineTest extends MediaWikiTestCase { $this->assertTrue( is_object( $results ) ); $matches = array(); - while ( $row = $results->next() ) { + $row = $results->next(); + while ( $row ) { $matches[] = $row->getTitle()->getPrefixedText(); + $row = $results->next(); } $results->free(); # Search is not guaranteed to return results in a certain order; @@ -83,20 +85,18 @@ class SearchEngineTest extends MediaWikiTestCase { * @param $n Integer: unused */ function insertPage( $pageName, $text, $ns ) { - $dbw = $this->db; $title = Title::newFromText( $pageName ); $user = User::newFromName( 'WikiSysop' ); $comment = 'Search Test'; // avoid memory leak...? - $linkCache = LinkCache::singleton(); - $linkCache->clear(); + LinkCache::singleton()->clear(); - $article = new Article( $title ); - $article->doEdit( $text, $comment, 0, false, $user ); + $page = WikiPage::factory( $title ); + $page->doEdit( $text, $comment, 0, false, $user ); - $this->pageList[] = array( $title, $article->getId() ); + $this->pageList[] = array( $title, $page->getId() ); return true; } diff --git a/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php new file mode 100644 index 00000000..a33c7b68 --- /dev/null +++ b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Test class to run the query of most of all our special pages + * + * Copyright © 2011, Antoine Musso + * + * @author Antoine Musso + * @group Database + */ + +if ( !defined( 'MEDIAWIKI' ) ) { + die( 1 ); +} + +global $IP; +require_once "$IP/includes/QueryPage.php"; // Needed to populate $wgQueryPages + +class QueryAllSpecialPagesTest extends MediaWikiTestCase { + + /** List query pages that can not be tested automatically */ + protected $manualTest = array( + 'LinkSearchPage' + ); + + /** + * Pages whose query use the same DB table more than once. + * This is used to skip testing those pages when run against a MySQL backend + * which does not support reopening a temporary table. See upstream bug: + * http://bugs.mysql.com/bug.php?id=10327 + */ + protected $reopensTempTable = array( + 'BrokenRedirects', + ); + + /** + * Initialize all query page objects + */ + function __construct() { + parent::__construct(); + + global $wgQueryPages; + foreach( $wgQueryPages as $page ) { + $class = $page[0]; + if( ! in_array( $class, $this->manualTest ) ) { + $this->queryPages[$class] = new $class; + } + } + } + + /** + * Test SQL for each of our QueryPages objects + * @group Database + */ + function testQuerypageSqlQuery() { + global $wgDBtype; + + foreach( $this->queryPages as $page ) { + + // With MySQL, skips special pages reopening a temporary table + // See http://bugs.mysql.com/bug.php?id=10327 + if( + $wgDBtype === 'mysql' + && in_array( $page->getName(), $this->reopensTempTable ) + ) { + $this->markTestSkipped( "SQL query for page {$page->getName()} can not be tested on MySQL backend (it reopens a temporary table)" ); + continue; + } + + $msg = "SQL query for page {$page->getName()} should give a result wrapper object" ; + + $result = $page->reallyDoQuery( 50 ); + if( $result instanceof ResultWrapper ) { + $this->assertTrue( true, $msg ); + } else { + $this->assertFalse( false, $msg ); + } + } + } +} diff --git a/tests/phpunit/includes/specials/SpecialRecentchanges.php b/tests/phpunit/includes/specials/SpecialRecentchangesTest.php index a98e7c1a..2e4f4b09 100644 --- a/tests/phpunit/includes/specials/SpecialRecentchanges.php +++ b/tests/phpunit/includes/specials/SpecialRecentchangesTest.php @@ -2,9 +2,10 @@ /** * Test class for SpecialRecentchanges class * - * Copyright © 2011, Ashar Voultoiz + * Copyright © 2011, Antoine Musso * - * @author Ashar Voultoiz + * @author Antoine Musso + * @group Database */ class SpecialRecentchangesTest extends MediaWikiTestCase { @@ -18,13 +19,12 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { /** helper to test SpecialRecentchanges::buildMainQueryConds() */ private function assertConditions( $expected, $requestOptions = null, $message = '' ) { - global $wgRequest; - $savedGlobal = $wgRequest; + $context = new RequestContext; + $context->setRequest( new FauxRequest( $requestOptions ) ); - # Initialize a WebRequest object ... - $wgRequest = new FauxRequest( $requestOptions ); - # ... then setup the rc object (which use wgRequest internally) + # setup the rc object $this->rc = new SpecialRecentChanges(); + $this->rc->setContext( $context ); $formOptions = $this->rc->setup( null ); # Filter out rc_timestamp conditions which depends on the test runtime @@ -40,8 +40,6 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { $queryConditions, $message ); - - $wgRequest = $savedGlobal; } /** return false if condition begin with 'rc_timestamp ' */ diff --git a/tests/phpunit/includes/specials/SpecialSearchTest.php b/tests/phpunit/includes/specials/SpecialSearchTest.php new file mode 100644 index 00000000..ea9d5533 --- /dev/null +++ b/tests/phpunit/includes/specials/SpecialSearchTest.php @@ -0,0 +1,108 @@ +<?php +/** + * Test class for SpecialSearch class + * Copyright © 2012, Antoine Musso + * + * @author Antoine Musso + * @group Database + */ + +class SpecialSearchTest extends MediaWikiTestCase { + private $search; + + function setUp() { } + function tearDown() { } + + /** + * @covers SpecialSearch::load + * @dataProvider provideSearchOptionsTests + * @param $requested Array Request parameters. For example array( 'ns5' => true, 'ns6' => true). NULL to use default options. + * @param $userOptions Array User options to test with. For example array('searchNs5' => 1 );. NULL to use default options. + * @param $expectedProfile An expected search profile name + * @param $expectedNs Array Expected namespaces + */ + function testProfileAndNamespaceLoading( + $requested, $userOptions, $expectedProfile, $expectedNS, + $message = 'Profile name and namespaces mismatches!' + ) { + $context = new RequestContext; + $context->setUser( + $this->newUserWithSearchNS( $userOptions ) + ); + /* + $context->setRequest( new FauxRequest( array( + 'ns5'=>true, + 'ns6'=>true, + ) )); + */ + $context->setRequest( new FauxRequest( $requested )); + $search = new SpecialSearch(); + $search->setContext( $context ); + $search->load(); + + /** + * Verify profile name and namespace in the same assertion to make + * sure we will be able to fully compare the above code. PHPUnit stop + * after an assertion fail. + */ + $this->assertEquals( + array( /** Expected: */ + 'ProfileName' => $expectedProfile, + 'Namespaces' => $expectedNS, + ) + , array( /** Actual: */ + 'ProfileName' => $search->getProfile(), + 'Namespaces' => $search->getNamespaces(), + ) + , $message + ); + + } + + function provideSearchOptionsTests() { + $defaultNS = SearchEngine::defaultNamespaces(); + $EMPTY_REQUEST = array(); + $NO_USER_PREF = null; + + return array( + /** + * Parameters: + * <Web Request>, <User options> + * Followed by expected values: + * <ProfileName>, <NSList> + * Then an optional message. + */ + array( + $EMPTY_REQUEST, $NO_USER_PREF, + 'default', $defaultNS, + 'Bug 33270: No request nor user preferences should give default profile' + ), + array( + array( 'ns5' => 1 ), $NO_USER_PREF, + 'advanced', array( 5), + 'Web request with specific NS should override user preference' + ), + array( + $EMPTY_REQUEST, array( 'searchNs2' => 1, 'searchNs14' => 1 ), + 'advanced', array( 2, 14 ), + 'Bug 33583: search with no option should honor User search preferences' + ), + ); + } + + /** + * Helper to create a new User object with given options + * User remains anonymous though + */ + function newUserWithSearchNS( $opt = null ) { + $u = User::newFromId(0); + if( $opt === null ) { + return $u; + } + foreach($opt as $name => $value) { + $u->setOption( $name, $value ); + } + return $u; + } +} + diff --git a/tests/phpunit/includes/upload/UploadFromUrlTest.php b/tests/phpunit/includes/upload/UploadFromUrlTest.php index 4722d408..d56cce31 100644 --- a/tests/phpunit/includes/upload/UploadFromUrlTest.php +++ b/tests/phpunit/includes/upload/UploadFromUrlTest.php @@ -20,7 +20,7 @@ class UploadFromUrlTest extends ApiTestCase { } } - protected function doApiRequest( $params, $unused = null, $appendModule = false ) { + protected function doApiRequest( $params, $unused = null, $appendModule = false, $user = null ) { $sessionId = session_id(); session_write_close(); @@ -36,7 +36,10 @@ class UploadFromUrlTest extends ApiTestCase { * Ensure that the job queue is empty before continuing */ public function testClearQueue() { - while ( $job = Job::pop() ) { } + $job = Job::pop(); + while ( $job ) { + $job = Job::pop(); + } $this->assertFalse( $job ); } @@ -73,7 +76,7 @@ class UploadFromUrlTest extends ApiTestCase { * @depends testClearQueue */ public function testSetupUrlDownload( $data ) { - $token = $this->user->editToken(); + $token = $this->user->getEditToken(); $exception = false; try { @@ -147,7 +150,7 @@ class UploadFromUrlTest extends ApiTestCase { * @depends testClearQueue */ public function testAsyncUpload( $data ) { - $token = $this->user->editToken(); + $token = $this->user->getEditToken(); $this->user->addGroup( 'users' ); @@ -166,7 +169,7 @@ class UploadFromUrlTest extends ApiTestCase { * @depends testClearQueue */ public function testAsyncUploadWarning( $data ) { - $token = $this->user->editToken(); + $token = $this->user->getEditToken(); $this->user->addGroup( 'users' ); @@ -197,7 +200,7 @@ class UploadFromUrlTest extends ApiTestCase { * @depends testClearQueue */ public function testSyncDownload( $data ) { - $token = $this->user->editToken(); + $token = $this->user->getEditToken(); $job = Job::pop(); $this->assertFalse( $job, 'Starting with an empty jobqueue' ); @@ -221,7 +224,7 @@ class UploadFromUrlTest extends ApiTestCase { } public function testLeaveMessage() { - $token = $this->user->user->editToken(); + $token = $this->user->user->getEditToken(); $talk = $this->user->user->getTalkPage(); if ( $talk->exists() ) { @@ -274,7 +277,7 @@ class UploadFromUrlTest extends ApiTestCase { return; - /** + /* // Broken until using leavemessage with ignorewarnings is supported $job->run(); diff --git a/tests/phpunit/includes/upload/UploadStashTest.php b/tests/phpunit/includes/upload/UploadStashTest.php index e644a259..c9dbb138 100644 --- a/tests/phpunit/includes/upload/UploadStashTest.php +++ b/tests/phpunit/includes/upload/UploadStashTest.php @@ -1,5 +1,7 @@ <?php - +/** + * @group Database + */ class UploadStashTest extends MediaWikiTestCase { /** * @var Array of UploadStashTestUser @@ -8,49 +10,68 @@ class UploadStashTest extends MediaWikiTestCase { public function setUp() { parent::setUp(); - + // Setup a file for bug 29408 $this->bug29408File = dirname( __FILE__ ) . '/bug29408'; - file_put_contents( $this->bug29408File, "\x00" ); - + file_put_contents( $this->bug29408File, "\x00" ); + self::$users = array( 'sysop' => new ApiTestUser( 'Uploadstashtestsysop', 'Upload Stash Test Sysop', - 'upload_stash_test_sysop@sample.com', + 'upload_stash_test_sysop@example.com', array( 'sysop' ) ), 'uploader' => new ApiTestUser( 'Uploadstashtestuser', 'Upload Stash Test User', - 'upload_stash_test_user@sample.com', + 'upload_stash_test_user@example.com', array() ) ); } - /** - * @group Database - */ public function testBug29408() { global $wgUser; $wgUser = self::$users['uploader']->user; - + $repo = RepoGroup::singleton()->getLocalRepo(); $stash = new UploadStash( $repo ); - + // Throws exception caught by PHPUnit on failure $file = $stash->stashFile( $this->bug29408File ); // We'll never reach this point if we hit bug 29408 $this->assertTrue( true, 'Unrecognized file without extension' ); - + $stash->removeFile( $file->getFileKey() ); } - + + public function testValidRequest() { + $request = new FauxRequest( array( 'wpFileKey' => 'foo') ); + $this->assertFalse( UploadFromStash::isValidRequest($request), 'Check failure on bad wpFileKey' ); + + $request = new FauxRequest( array( 'wpSessionKey' => 'foo') ); + $this->assertFalse( UploadFromStash::isValidRequest($request), 'Check failure on bad wpSessionKey' ); + + $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test') ); + $this->assertTrue( UploadFromStash::isValidRequest($request), 'Check good wpFileKey' ); + + $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test') ); + $this->assertTrue( UploadFromStash::isValidRequest($request), 'Check good wpSessionKey' ); + + $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test', 'wpSessionKey' => 'foo') ); + $this->assertTrue( UploadFromStash::isValidRequest($request), 'Check key precedence' ); + } + public function tearDown() { parent::tearDown(); - - unlink( $this->bug29408File . "." ); - + + if( file_exists( $this->bug29408File . "." ) ) { + unlink( $this->bug29408File . "." ); + } + + if( file_exists( $this->bug29408File ) ) { + unlink( $this->bug29408File ); + } } } diff --git a/tests/phpunit/includes/upload/UploadTest.php b/tests/phpunit/includes/upload/UploadTest.php index 69c29032..4293d23b 100644 --- a/tests/phpunit/includes/upload/UploadTest.php +++ b/tests/phpunit/includes/upload/UploadTest.php @@ -20,53 +20,14 @@ class UploadTest extends MediaWikiTestCase { $wgHooks = $this->hooks; } - /** - * Test various forms of valid and invalid titles that can be supplied. - */ - public function testTitleValidation() { - - - /* Test a valid title */ - $this->assertUploadTitleAndCode( 'ValidTitle.jpg', - 'ValidTitle.jpg', UploadBase::OK, - 'upload valid title' ); - - /* A title with a slash */ - $this->assertUploadTitleAndCode( 'A/B.jpg', - 'B.jpg', UploadBase::OK, - 'upload title with slash' ); - - /* A title with illegal char */ - $this->assertUploadTitleAndCode( 'A:B.jpg', - 'A-B.jpg', UploadBase::OK, - 'upload title with colon' ); - - /* Stripping leading File: prefix */ - $this->assertUploadTitleAndCode( 'File:C.jpg', - 'C.jpg', UploadBase::OK, - 'upload title with File prefix' ); - /* Test illegal suggested title (r94601) */ - $this->assertUploadTitleAndCode( '%281%29.JPG', - null, UploadBase::ILLEGAL_FILENAME, - 'illegal title for upload' ); - - /* A title without extension */ - $this->assertUploadTitleAndCode( 'A', - null, UploadBase::FILETYPE_MISSING, - 'upload title without extension' ); - - /* A title with no basename */ - $this->assertUploadTitleAndCode( '.jpg', - null, UploadBase::MIN_LENGTH_PARTNAME, - 'upload title without basename' ); - - } /** - * Helper function for testTitleValidation. First checks the return code - * of UploadBase::getTitle() and then the actual returned titl + * First checks the return code + * of UploadBase::getTitle() and then the actual returned title + * + * @dataProvider dataTestTitleValidation */ - private function assertUploadTitleAndCode( $srcFilename, $dstFilename, $code, $msg ) { + public function testTitleValidation( $srcFilename, $dstFilename, $code, $msg ) { /* Check the result code */ $this->assertEquals( $code, $this->upload->testTitleValidation( $srcFilename ), @@ -79,6 +40,41 @@ class UploadTest extends MediaWikiTestCase { "$msg text" ); } } + + /** + * Test various forms of valid and invalid titles that can be supplied. + */ + public function dataTestTitleValidation() { + return array( + /* Test a valid title */ + array( 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK, + 'upload valid title' ), + /* A title with a slash */ + array( 'A/B.jpg', 'B.jpg', UploadBase::OK, + 'upload title with slash' ), + /* A title with illegal char */ + array( 'A:B.jpg', 'A-B.jpg', UploadBase::OK, + 'upload title with colon' ), + /* Stripping leading File: prefix */ + array( 'File:C.jpg', 'C.jpg', UploadBase::OK, + 'upload title with File prefix' ), + /* Test illegal suggested title (r94601) */ + array( '%281%29.JPG', null, UploadBase::ILLEGAL_FILENAME, + 'illegal title for upload' ), + /* A title without extension */ + array( 'A', null, UploadBase::FILETYPE_MISSING, + 'upload title without extension' ), + /* A title with no basename */ + array( '.jpg', null, UploadBase::MIN_LENGTH_PARTNAME, + 'upload title without basename' ), + /* A title that is longer than 255 bytes */ + array( str_repeat( 'a', 255 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, + 'upload title longer than 255 bytes' ), + /* A title that is longer than 240 bytes */ + array( str_repeat( 'a', 240 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, + 'upload title longer than 240 bytes' ), + ); + } /** * Test the upload verification functions @@ -104,7 +100,7 @@ class UploadTest extends MediaWikiTestCase { } /** - * test uploading a 100 bytes file with wgMaxUploadSize = 100 + * test uploading a 100 bytes file with $wgMaxUploadSize = 100 * * This method should be abstracted so we can test different settings. */ @@ -134,6 +130,7 @@ class UploadTestHandler extends UploadBase { public function testTitleValidation( $name ) { $this->mTitle = false; $this->mDesiredDestName = $name; + $this->mTitleError = UploadBase::OK; $this->getTitle(); return $this->mTitleError; } diff --git a/tests/phpunit/languages/LanguageAmTest.php b/tests/phpunit/languages/LanguageAmTest.php new file mode 100644 index 00000000..3a648ded --- /dev/null +++ b/tests/phpunit/languages/LanguageAmTest.php @@ -0,0 +1,33 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageAm.php */ +class LanguageAmTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Am' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePlural() { + return array ( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 200 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageArTest.php b/tests/phpunit/languages/LanguageArTest.php new file mode 100644 index 00000000..b23e0534 --- /dev/null +++ b/tests/phpunit/languages/LanguageArTest.php @@ -0,0 +1,78 @@ +<?php +/** + * Based on LanguagMlTest + * @file + */ + +/** Tests for MediaWiki languages/LanguageAr.php */ +class LanguageArTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Ar' ); + } + function tearDown() { + unset( $this->lang ); + } + + function testFormatNum() { + $this->assertEquals( '١٬٢٣٤٬٥٦٧', $this->lang->formatNum( '1234567' ) ); + $this->assertEquals( '-١٢٫٨٩', $this->lang->formatNum( -12.89 ) ); + } + + /** + * Mostly to test the raw ascii feature. + * @dataProvider providerSprintfDate + */ + function testSprintfDate( $format, $date, $expected ) { + $this->assertEquals( $expected, $this->lang->sprintfDate( $format, $date ) ); + } + + function providerSprintfDate() { + return array( + array( + 'xg "vs" g', + '20120102030410', + 'يناير vs ٣' + ), + array( + 'xmY', + '20120102030410', + '١٤٣٣' + ), + array( + 'xnxmY', + '20120102030410', + '1433' + ), + array( + 'xN xmj xmn xN xmY', + '20120102030410', + ' 7 2 ١٤٣٣' + ), + ); + } + /** @dataProvider providePlural */ + function testPlural( $result, $value ) { + $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + function providePlural() { + return array ( + array( 'zero', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'few', 3 ), + array( 'few', 9 ), + array( 'few', 110 ), + array( 'many', 11 ), + array( 'many', 15 ), + array( 'many', 99 ), + array( 'many', 9999 ), + array( 'other', 100 ), + array( 'other', 102 ), + array( 'other', 1000 ), + array( 'other', 1.7 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageBeTest.php b/tests/phpunit/languages/LanguageBeTest.php new file mode 100644 index 00000000..735ccc63 --- /dev/null +++ b/tests/phpunit/languages/LanguageBeTest.php @@ -0,0 +1,40 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageBe.php */ +class LanguageBeTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Be' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePlural() { + return array ( + array( 'one', 1 ), + array( 'many', 11 ), + array( 'one', 91 ), + array( 'one', 121 ), + array( 'few', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), + array( 'few', 334 ), + array( 'many', 5 ), + array( 'many', 15 ), + array( 'many', 120 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageBe_taraskTest.php b/tests/phpunit/languages/LanguageBe_taraskTest.php index e7fdb7ca..765cdb8f 100644 --- a/tests/phpunit/languages/LanguageBe_taraskTest.php +++ b/tests/phpunit/languages/LanguageBe_taraskTest.php @@ -27,4 +27,39 @@ class LanguageBeTaraskTest extends MediaWikiTestCase { function testDoesNotCommafyFourDigitsNumber() { $this->assertEquals( '1234', $this->lang->commafy( '1234' ) ); } + /** @dataProvider providePluralFourForms */ + function testPluralFourForms( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePluralFourForms() { + return array ( + array( 'one', 1 ), + array( 'many', 11 ), + array( 'one', 91 ), + array( 'one', 121 ), + array( 'few', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), + array( 'few', 334 ), + array( 'many', 5 ), + array( 'many', 15 ), + array( 'many', 120 ), + ); + } + /** @dataProvider providePluralTwoForms */ + function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'several' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + function providePluralTwoForms() { + return array ( + array( 'one', 1 ), + array( 'several', 11 ), + array( 'several', 91 ), + array( 'several', 121 ), + ); + } + } diff --git a/tests/phpunit/languages/LanguageBhTest.php b/tests/phpunit/languages/LanguageBhTest.php new file mode 100644 index 00000000..e1e2a13e --- /dev/null +++ b/tests/phpunit/languages/LanguageBhTest.php @@ -0,0 +1,34 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageBh.php */ +class LanguageBhTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Bh' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePlural() { + return array ( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageBsTest.php b/tests/phpunit/languages/LanguageBsTest.php new file mode 100644 index 00000000..b6631c03 --- /dev/null +++ b/tests/phpunit/languages/LanguageBsTest.php @@ -0,0 +1,41 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageBs.php */ +class LanguageBsTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Bs' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePlural() { + return array ( + array( 'many', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 4 ), + array( 'many', 5 ), + array( 'many', 11 ), + array( 'many', 20 ), + array( 'one', 21 ), + array( 'few', 24 ), + array( 'many', 25 ), + array( 'many', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageCsTest.php b/tests/phpunit/languages/LanguageCsTest.php new file mode 100644 index 00000000..dda29f9a --- /dev/null +++ b/tests/phpunit/languages/LanguageCsTest.php @@ -0,0 +1,40 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/Languagecs.php */ +class LanguageCsTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'cs' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), + array( 'other', 5 ), + array( 'other', 11 ), + array( 'other', 20 ), + array( 'other', 25 ), + array( 'other', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageCuTest.php b/tests/phpunit/languages/LanguageCuTest.php new file mode 100644 index 00000000..f8186d7b --- /dev/null +++ b/tests/phpunit/languages/LanguageCuTest.php @@ -0,0 +1,41 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageCu.php */ +class LanguageCuTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'cu' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'many', 3 ), + array( 'many', 4 ), + array( 'other', 5 ), + array( 'one', 11 ), + array( 'other', 20 ), + array( 'few', 22 ), + array( 'many', 223 ), + array( 'other', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageCyTest.php b/tests/phpunit/languages/LanguageCyTest.php new file mode 100644 index 00000000..e9f9e410 --- /dev/null +++ b/tests/phpunit/languages/LanguageCyTest.php @@ -0,0 +1,42 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageCy.php */ +class LanguageCyTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'cy' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'zero', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'few', 3 ), + array( 'many', 6 ), + array( 'other', 4 ), + array( 'other', 5 ), + array( 'other', 11 ), + array( 'other', 20 ), + array( 'other', 22 ), + array( 'other', 223 ), + array( 'other', 200.00 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageDsbTest.php b/tests/phpunit/languages/LanguageDsbTest.php new file mode 100644 index 00000000..ab7f9313 --- /dev/null +++ b/tests/phpunit/languages/LanguageDsbTest.php @@ -0,0 +1,40 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageDsb.php */ +class LanguageDsbTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'dsb' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePlural() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'one', 101 ), + array( 'one', 90001 ), + array( 'two', 2 ), + array( 'few', 3 ), + array( 'few', 203 ), + array( 'few', 4 ), + array( 'other', 99 ), + array( 'other', 555 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageFrTest.php b/tests/phpunit/languages/LanguageFrTest.php new file mode 100644 index 00000000..8538744e --- /dev/null +++ b/tests/phpunit/languages/LanguageFrTest.php @@ -0,0 +1,34 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageFr.php */ +class LanguageFrTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'fr' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePlural() { + return array ( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageGaTest.php b/tests/phpunit/languages/LanguageGaTest.php new file mode 100644 index 00000000..fbd9f11d --- /dev/null +++ b/tests/phpunit/languages/LanguageGaTest.php @@ -0,0 +1,34 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageGa.php */ +class LanguageGaTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'ga' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'other', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageGdTest.php b/tests/phpunit/languages/LanguageGdTest.php new file mode 100644 index 00000000..24574bda --- /dev/null +++ b/tests/phpunit/languages/LanguageGdTest.php @@ -0,0 +1,38 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageGd.php */ +class LanguageGdTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'gd' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + // The CLDR ticket for this plural forms is not same as mw plural forms. See http://unicode.org/cldr/trac/ticket/2883 + $forms = array( 'Form 1', 'Form 2', 'Form 3', 'Form 4', 'Form 5', 'Form 6' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + function providerPlural() { + return array ( + array( 'Form 6', 0 ), + array( 'Form 1', 1 ), + array( 'Form 2', 2 ), + array( 'Form 3', 11 ), + array( 'Form 4', 12 ), + array( 'Form 5', 3 ), + array( 'Form 5', 19 ), + array( 'Form 6', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageGvTest.php b/tests/phpunit/languages/LanguageGvTest.php new file mode 100644 index 00000000..3d298b9b --- /dev/null +++ b/tests/phpunit/languages/LanguageGvTest.php @@ -0,0 +1,39 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageGv.php */ +class LanguageGvTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'gv' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + // This is not compatible with CLDR plural rules http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#gv + $forms = array( 'Form 1', 'Form 2', 'Form 3', 'Form 4' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + function providerPlural() { + return array ( + array( 'Form 4', 0 ), + array( 'Form 2', 1 ), + array( 'Form 3', 2 ), + array( 'Form 4', 3 ), + array( 'Form 1', 20 ), + array( 'Form 2', 21 ), + array( 'Form 3', 22 ), + array( 'Form 4', 23 ), + array( 'Form 4', 50 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageHeTest.php b/tests/phpunit/languages/LanguageHeTest.php new file mode 100644 index 00000000..9ac0f952 --- /dev/null +++ b/tests/phpunit/languages/LanguageHeTest.php @@ -0,0 +1,48 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageHe.php */ +class LanguageHeTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'he' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPluralDual */ + function testPluralDual( $result, $value ) { + $forms = array( 'one', 'many', 'two' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPluralDual() { + return array ( + array( 'many', 0 ), // Zero -> plural + array( 'one', 1 ), // Singular + array( 'two', 2 ), // Dual + array( 'many', 3 ), // Plural + ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'many' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'many', 0 ), // Zero -> plural + array( 'one', 1 ), // Singular + array( 'many', 2 ), // Plural, no dual provided + array( 'many', 3 ), // Plural + ); + } +} diff --git a/tests/phpunit/languages/LanguageHiTest.php b/tests/phpunit/languages/LanguageHiTest.php new file mode 100644 index 00000000..ead9e020 --- /dev/null +++ b/tests/phpunit/languages/LanguageHiTest.php @@ -0,0 +1,34 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageHi.php */ +class LanguageHiTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Hi' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePlural() { + return array ( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageHrTest.php b/tests/phpunit/languages/LanguageHrTest.php new file mode 100644 index 00000000..4f1c66bf --- /dev/null +++ b/tests/phpunit/languages/LanguageHrTest.php @@ -0,0 +1,41 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageHr.php */ +class LanguageHrTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'hr' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'many', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 4 ), + array( 'many', 5 ), + array( 'many', 11 ), + array( 'many', 20 ), + array( 'one', 21 ), + array( 'few', 24 ), + array( 'many', 25 ), + array( 'many', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageHsbTest.php b/tests/phpunit/languages/LanguageHsbTest.php new file mode 100644 index 00000000..803c7721 --- /dev/null +++ b/tests/phpunit/languages/LanguageHsbTest.php @@ -0,0 +1,40 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageHsb.php */ +class LanguageHsbTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'hsb' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePlural() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'one', 101 ), + array( 'one', 90001 ), + array( 'two', 2 ), + array( 'few', 3 ), + array( 'few', 203 ), + array( 'few', 4 ), + array( 'other', 99 ), + array( 'other', 555 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageHyTest.php b/tests/phpunit/languages/LanguageHyTest.php new file mode 100644 index 00000000..7990bdfc --- /dev/null +++ b/tests/phpunit/languages/LanguageHyTest.php @@ -0,0 +1,34 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageHy.php */ +class LanguageHyTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'hy' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageKshTest.php b/tests/phpunit/languages/LanguageKshTest.php new file mode 100644 index 00000000..ab889464 --- /dev/null +++ b/tests/phpunit/languages/LanguageKshTest.php @@ -0,0 +1,34 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageKsh.php */ +class LanguageKshTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'ksh' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other', 'zero' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'zero', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageLnTest.php b/tests/phpunit/languages/LanguageLnTest.php new file mode 100644 index 00000000..0fd9167e --- /dev/null +++ b/tests/phpunit/languages/LanguageLnTest.php @@ -0,0 +1,34 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageLn.php */ +class LanguageLnTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'ln' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePlural() { + return array ( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageLtTest.php b/tests/phpunit/languages/LanguageLtTest.php new file mode 100644 index 00000000..0d7c7d3e --- /dev/null +++ b/tests/phpunit/languages/LanguageLtTest.php @@ -0,0 +1,53 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageLt.php */ +class LanguageLtTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Lt' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider provideOneFewOtherCases */ + function testOneFewOtherPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + /** @dataProvider provideOneFewCases */ + function testOneFewPlural( $result, $value ) { + $forms = array( 'one', 'few' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function provideOneFewOtherCases() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 9 ), + array( 'other', 10 ), + array( 'other', 11 ), + array( 'other', 20 ), + array( 'one', 21 ), + array( 'few', 32 ), + array( 'one', 41 ), + array( 'one', 40001 ), + ); + } + + function provideOneFewCases() { + return array ( + array( 'one', 1 ), + array( 'few', 15 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageLvTest.php b/tests/phpunit/languages/LanguageLvTest.php new file mode 100644 index 00000000..0636da5f --- /dev/null +++ b/tests/phpunit/languages/LanguageLvTest.php @@ -0,0 +1,39 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageLv.php */ +class LanguageLvTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'lv' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'other', 0 ), #this must be zero form as per CLDR + array( 'one', 1 ), + array( 'other', 11 ), + array( 'one', 21 ), + array( 'other', 411 ), + array( 'other', 12.345 ), + array( 'other', 20 ), + array( 'one', 31 ), + array( 'other', 200 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageMgTest.php b/tests/phpunit/languages/LanguageMgTest.php new file mode 100644 index 00000000..06b56547 --- /dev/null +++ b/tests/phpunit/languages/LanguageMgTest.php @@ -0,0 +1,35 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageMg.php */ +class LanguageMgTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'mg' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePlural() { + return array ( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 200 ), + array( 'other', 123.3434 ), + ); + } + +} diff --git a/tests/phpunit/languages/LanguageMkTest.php b/tests/phpunit/languages/LanguageMkTest.php new file mode 100644 index 00000000..cf5ec3d9 --- /dev/null +++ b/tests/phpunit/languages/LanguageMkTest.php @@ -0,0 +1,41 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageMk.php */ +class LanguageMkTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'mk' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + + function providerPlural() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'other', 11 ), + array( 'one', 21 ), + array( 'other', 411 ), + array( 'other', 12.345 ), + array( 'other', 20 ), + array( 'one', 31 ), + array( 'other', 200 ), + ); + } + + +} diff --git a/tests/phpunit/languages/LanguageMlTest.php b/tests/phpunit/languages/LanguageMlTest.php new file mode 100644 index 00000000..8c4b0b2f --- /dev/null +++ b/tests/phpunit/languages/LanguageMlTest.php @@ -0,0 +1,43 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2011, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageMl.php */ +class LanguageMlTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Ml' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** see bug 29495 */ + /** @dataProvider providerFormatNum*/ + function testFormatNum( $result, $value ) { + $this->assertEquals( $result, $this->lang->formatNum( $value ) ); + } + + function providerFormatNum() { + return array( + array( '12,34,567', '1234567' ), + array( '12,345', '12345' ), + array( '1', '1' ), + array( '123', '123' ) , + array( '1,234', '1234' ), + array( '12,345.56', '12345.56' ), + array( '12,34,56,79,81,23,45,678', '12345679812345678' ), + array( '.12345', '.12345' ), + array( '-12,00,000', '-1200000' ), + array( '-98', '-98' ), + array( '-98', -98 ), + array( '-1,23,45,678', -12345678 ), + array( '', '' ), + array( '', null ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageMoTest.php b/tests/phpunit/languages/LanguageMoTest.php new file mode 100644 index 00000000..533e590f --- /dev/null +++ b/tests/phpunit/languages/LanguageMoTest.php @@ -0,0 +1,43 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageMo.php */ +class LanguageMoTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'mo' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 19 ), + array( 'other', 20 ), + array( 'other', 99 ), + array( 'other', 100 ), + array( 'few', 101 ), + array( 'few', 119 ), + array( 'other', 120 ), + array( 'other', 200 ), + array( 'few', 201 ), + array( 'few', 219 ), + array( 'other', 220 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageMtTest.php b/tests/phpunit/languages/LanguageMtTest.php new file mode 100644 index 00000000..421bb388 --- /dev/null +++ b/tests/phpunit/languages/LanguageMtTest.php @@ -0,0 +1,72 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageMt.php */ +class LanguageMtTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'mt' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPluralAllForms */ + function testPluralAllForms( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPluralAllForms() { + return array ( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 10 ), + array( 'many', 11 ), + array( 'many', 19 ), + array( 'other', 20 ), + array( 'other', 99 ), + array( 'other', 100 ), + array( 'other', 101 ), + array( 'few', 102 ), + array( 'few', 110 ), + array( 'many', 111 ), + array( 'many', 119 ), + array( 'other', 120 ), + array( 'other', 201 ), + ); + } + + /** @dataProvider providerPluralTwoForms */ + function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'many' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPluralTwoForms() { + return array ( + array( 'many', 0 ), + array( 'one', 1 ), + array( 'many', 2 ), + array( 'many', 10 ), + array( 'many', 11 ), + array( 'many', 19 ), + array( 'many', 20 ), + array( 'many', 99 ), + array( 'many', 100 ), + array( 'many', 101 ), + array( 'many', 102 ), + array( 'many', 110 ), + array( 'many', 111 ), + array( 'many', 119 ), + array( 'many', 120 ), + array( 'many', 201 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageNlTest.php b/tests/phpunit/languages/LanguageNlTest.php new file mode 100644 index 00000000..cf979cd2 --- /dev/null +++ b/tests/phpunit/languages/LanguageNlTest.php @@ -0,0 +1,28 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2011, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageNl.php */ +class LanguageNlTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Nl' ); + } + function tearDown() { + unset( $this->lang ); + } + + function testFormatNum() { + $this->assertEquals( '1.234.567', $this->lang->formatNum( '1234567' ) ); + $this->assertEquals( '12.345', $this->lang->formatNum( '12345' ) ); + $this->assertEquals( '1', $this->lang->formatNum( '1' ) ); + $this->assertEquals( '123', $this->lang->formatNum( '123' ) ); + $this->assertEquals( '1.234', $this->lang->formatNum( '1234' ) ); + $this->assertEquals( '12.345,56', $this->lang->formatNum( '12345.56' ) ); + $this->assertEquals( ',1234556', $this->lang->formatNum( '.1234556' ) ); + } +} diff --git a/tests/phpunit/languages/LanguageNsoTest.php b/tests/phpunit/languages/LanguageNsoTest.php new file mode 100644 index 00000000..ea393628 --- /dev/null +++ b/tests/phpunit/languages/LanguageNsoTest.php @@ -0,0 +1,32 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageNso.php */ +class LanguageNsoTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'nso' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'many' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'many', 2 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguagePlTest.php b/tests/phpunit/languages/LanguagePlTest.php new file mode 100644 index 00000000..e56d4b77 --- /dev/null +++ b/tests/phpunit/languages/LanguagePlTest.php @@ -0,0 +1,72 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguagePl.php */ +class LanguagePlTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'pl' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPluralFourForms */ + function testPluralFourForms( $result, $value ) { + $forms = array( 'one', 'few', 'many' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPluralFourForms() { + return array ( + array( 'many', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), + array( 'many', 5 ), + array( 'many', 9 ), + array( 'many', 10 ), + array( 'many', 11 ), + array( 'many', 21 ), + array( 'few', 22 ), + array( 'few', 23 ), + array( 'few', 24 ), + array( 'many', 25 ), + array( 'many', 200 ), + array( 'many', 201 ), + ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'many' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'many', 0 ), + array( 'one', 1 ), + array( 'many', 2 ), + array( 'many', 3 ), + array( 'many', 4 ), + array( 'many', 5 ), + array( 'many', 9 ), + array( 'many', 10 ), + array( 'many', 11 ), + array( 'many', 21 ), + array( 'many', 22 ), + array( 'many', 23 ), + array( 'many', 24 ), + array( 'many', 25 ), + array( 'many', 200 ), + array( 'many', 201 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageRoTest.php b/tests/phpunit/languages/LanguageRoTest.php new file mode 100644 index 00000000..5270f6fe --- /dev/null +++ b/tests/phpunit/languages/LanguageRoTest.php @@ -0,0 +1,43 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageRo.php */ +class LanguageRoTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'ro' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 19 ), + array( 'other', 20 ), + array( 'other', 99 ), + array( 'other', 100 ), + array( 'few', 101 ), + array( 'few', 119 ), + array( 'other', 120 ), + array( 'other', 200 ), + array( 'few', 201 ), + array( 'few', 219 ), + array( 'other', 220 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageRuTest.php b/tests/phpunit/languages/LanguageRuTest.php new file mode 100644 index 00000000..7a1f193b --- /dev/null +++ b/tests/phpunit/languages/LanguageRuTest.php @@ -0,0 +1,54 @@ +<?php +/** + * @author Amir E. Aharoni + * based on LanguageBe_tarask.php + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageRu.php */ +class LanguageRuTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'ru' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePluralFourForms */ + function testPluralFourForms( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePluralFourForms() { + return array ( + array( 'one', 1 ), + array( 'many', 11 ), + array( 'one', 91 ), + array( 'one', 121 ), + array( 'few', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), + array( 'few', 334 ), + array( 'many', 5 ), + array( 'many', 15 ), + array( 'many', 120 ), + ); + } + /** @dataProvider providePluralTwoForms */ + function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'several' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + function providePluralTwoForms() { + return array ( + array( 'one', 1 ), + array( 'several', 11 ), + array( 'several', 91 ), + array( 'several', 121 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageSeTest.php b/tests/phpunit/languages/LanguageSeTest.php new file mode 100644 index 00000000..065ec29e --- /dev/null +++ b/tests/phpunit/languages/LanguageSeTest.php @@ -0,0 +1,48 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageSe.php */ +class LanguageSeTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'se' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPluralThreeForms */ + function testPluralThreeForms( $result, $value ) { + $forms = array( 'one', 'two', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPluralThreeForms() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'other', 3 ), + ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 3 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageSgsTest.php b/tests/phpunit/languages/LanguageSgsTest.php new file mode 100644 index 00000000..931c82f0 --- /dev/null +++ b/tests/phpunit/languages/LanguageSgsTest.php @@ -0,0 +1,66 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageSgs.php */ +class LanguageSgsTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Sgs' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePluralAllForms */ + function testPluralAllForms( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePluralAllForms() { + return array ( + array( 'many', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'other', 3 ), + array( 'many', 10 ), + array( 'many', 11 ), + array( 'many', 12 ), + array( 'many', 19 ), + array( 'other', 20 ), + array( 'many', 100 ), + array( 'one', 101 ), + array( 'many', 111 ), + array( 'many', 112 ), + ); + } + + /** @dataProvider providePluralTwoForms */ + function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePluralTwoForms() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 3 ), + array( 'other', 10 ), + array( 'other', 11 ), + array( 'other', 12 ), + array( 'other', 19 ), + array( 'other', 20 ), + array( 'other', 100 ), + array( 'one', 101 ), + array( 'other', 111 ), + array( 'other', 112 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageShTest.php b/tests/phpunit/languages/LanguageShTest.php new file mode 100644 index 00000000..b8169aed --- /dev/null +++ b/tests/phpunit/languages/LanguageShTest.php @@ -0,0 +1,32 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageSh.php */ +class LanguageShTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'sh' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'many' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'many', 0 ), + array( 'one', 1 ), + array( 'many', 2 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageSkTest.php b/tests/phpunit/languages/LanguageSkTest.php new file mode 100644 index 00000000..4cfd840e --- /dev/null +++ b/tests/phpunit/languages/LanguageSkTest.php @@ -0,0 +1,40 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Amir E. Aharoni + * based on LanguageSkTest.php + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageSk.php */ +class LanguageSkTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'sk' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), + array( 'other', 5 ), + array( 'other', 11 ), + array( 'other', 20 ), + array( 'other', 25 ), + array( 'other', 200 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageSlTest.php b/tests/phpunit/languages/LanguageSlTest.php new file mode 100644 index 00000000..c1f75691 --- /dev/null +++ b/tests/phpunit/languages/LanguageSlTest.php @@ -0,0 +1,42 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Amir E. Aharoni + * based on LanguageSkTest.php + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageSl.php */ +class LanguageSlTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'sl' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other', 'zero' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'zero', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), + array( 'other', 5 ), + array( 'other', 99 ), + array( 'other', 100 ), + array( 'one', 101 ), + array( 'two', 102 ), + array( 'few', 103 ), + array( 'one', 201 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageSmaTest.php b/tests/phpunit/languages/LanguageSmaTest.php new file mode 100644 index 00000000..b7e72e97 --- /dev/null +++ b/tests/phpunit/languages/LanguageSmaTest.php @@ -0,0 +1,48 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageSma.php */ +class LanguageSmaTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'sma' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPluralThreeForms */ + function testPluralThreeForms( $result, $value ) { + $forms = array( 'one', 'two', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPluralThreeForms() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'other', 3 ), + ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 3 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageSrTest.php b/tests/phpunit/languages/LanguageSrTest.php new file mode 100644 index 00000000..a50547c6 --- /dev/null +++ b/tests/phpunit/languages/LanguageSrTest.php @@ -0,0 +1,199 @@ +<?php +/** + * PHPUnit tests for the Serbian language. + * The language can be represented using two scripts: + * - Latin (SR_el) + * - Cyrillic (SR_ec) + * Both representations seems to be bijective, hence MediaWiki can convert + * from one script to the other. + * + * @author Antoine Musso <hashar at free dot fr> + * @copyright Copyright © 2011, Antoine Musso <hashar at free dot fr> + * @file + */ + +require_once dirname( dirname( __FILE__ ) ) . '/bootstrap.php'; + +/** Tests for MediaWiki languages/LanguageTr.php */ +class LanguageSrTest extends MediaWikiTestCase { + /* Language object. Initialized before each test */ + private $lang; + + function setUp() { + $this->lang = Language::factory( 'sr' ); + } + function tearDown() { + unset( $this->lang ); + } + + ##### TESTS ####################################################### + + function testEasyConversions( ) { + $this->assertCyrillic( + 'шђчћжШЂЧЋЖ', + 'Cyrillic guessing characters' + ); + $this->assertLatin( + 'šđč枊ĐČĆŽ', + 'Latin guessing characters' + ); + } + + function testMixedConversions() { + $this->assertCyrillic( + 'шђчћжШЂЧЋЖ - šđčćž', + 'Mostly cyrillic characters' + ); + $this->assertLatin( + 'šđč枊ĐČĆŽ - шђчћж', + 'Mostly latin characters' + ); + } + + function testSameAmountOfLatinAndCyrillicGetConverted() { + $this->assertConverted( + '4 latin: šđčć | 4 cyrillic: шђчћ', + 'sr-ec' + ); + $this->assertConverted( + '4 latin: šđčć | 4 cyrillic: шђчћ', + 'sr-el' + ); + } + + /** + * @author Nikola Smolenski + */ + function testConversionToCyrillic() { + $this->assertEquals( 'абвг', + $this->convertToCyrillic( 'abvg' ) + ); + $this->assertEquals( 'абвг', + $this->convertToCyrillic( 'абвг' ) + ); + $this->assertEquals( 'abvgшђжчћ', + $this->convertToCyrillic( 'abvgшђжчћ' ) + ); + $this->assertEquals( 'абвгшђжчћ', + $this->convertToCyrillic( 'абвгšđžčć' ) + ); + // Roman numerals are not converted + $this->assertEquals( 'а I б II в III г IV шђжчћ', + $this->convertToCyrillic( 'a I b II v III g IV šđžčć' ) + ); + } + + function testConversionToLatin() { + $this->assertEquals( 'abcd', + $this->convertToLatin( 'abcd' ) + ); + $this->assertEquals( 'abcd', + $this->convertToLatin( 'абцд' ) + ); + $this->assertEquals( 'abcdšđžčć', + $this->convertToLatin( 'abcdшђжчћ' ) + ); + $this->assertEquals( 'абцдšđžčć', + $this->convertToLatin( 'абцдšđžčć' ) + ); + } + + /** @dataProvider providePluralFourForms */ + function testPluralFourForms( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePluralFourForms() { + return array ( + array( 'one', 1 ), + array( 'many', 11 ), + array( 'one', 91 ), + array( 'one', 121 ), + array( 'few', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), + array( 'few', 334 ), + array( 'many', 5 ), + array( 'many', 15 ), + array( 'many', 120 ), + ); + } + /** @dataProvider providePluralTwoForms */ + function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'several' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + function providePluralTwoForms() { + return array ( + array( 'one', 1 ), + array( 'several', 11 ), + array( 'several', 91 ), + array( 'several', 121 ), + ); + } + + ##### HELPERS ##################################################### + /** + *Wrapper to verify text stay the same after applying conversion + * @param $text string Text to convert + * @param $variant string Language variant 'sr-ec' or 'sr-el' + * @param $msg string Optional message + */ + function assertUnConverted( $text, $variant, $msg = '' ) { + $this->assertEquals( + $text, + $this->convertTo( $text, $variant ), + $msg + ); + } + /** + * Wrapper to verify a text is different once converted to a variant. + * @param $text string Text to convert + * @param $variant string Language variant 'sr-ec' or 'sr-el' + * @param $msg string Optional message + */ + function assertConverted( $text, $variant, $msg = '' ) { + $this->assertNotEquals( + $text, + $this->convertTo( $text, $variant ), + $msg + ); + } + + /** + * Verifiy the given Cyrillic text is not converted when using + * using the cyrillic variant and converted to Latin when using + * the Latin variant. + */ + function assertCyrillic( $text, $msg = '' ) { + $this->assertUnConverted( $text, 'sr-ec', $msg ); + $this->assertConverted( $text, 'sr-el', $msg ); + } + /** + * Verifiy the given Latin text is not converted when using + * using the Latin variant and converted to Cyrillic when using + * the Cyrillic variant. + */ + function assertLatin( $text, $msg = '' ) { + $this->assertUnConverted( $text, 'sr-el', $msg ); + $this->assertConverted( $text, 'sr-ec', $msg ); + } + + + /** Wrapper for converter::convertTo() method*/ + function convertTo( $text, $variant ) { + return $this + ->lang + ->mConverter + ->convertTo( + $text, $variant + ); + } + function convertToCyrillic( $text ) { + return $this->convertTo( $text, 'sr-ec' ); + } + function convertToLatin( $text ) { + return $this->convertTo( $text, 'sr-el' ); + } +} diff --git a/tests/phpunit/languages/LanguageTest.php b/tests/phpunit/languages/LanguageTest.php index aaad9c31..c83e01ea 100644 --- a/tests/phpunit/languages/LanguageTest.php +++ b/tests/phpunit/languages/LanguageTest.php @@ -1,6 +1,10 @@ <?php class LanguageTest extends MediaWikiTestCase { + + /** + * @var Language + */ private $lang; function setUp() { @@ -19,97 +23,46 @@ class LanguageTest extends MediaWikiTestCase { 'convertDoubleWidth() with the full alphabet and digits' ); } - - function testFormatTimePeriod() { - $this->assertEquals( - "9.5s", - $this->lang->formatTimePeriod( 9.45 ), - 'formatTimePeriod() rounding (<10s)' - ); - - $this->assertEquals( - "10s", - $this->lang->formatTimePeriod( 9.95 ), - 'formatTimePeriod() rounding (<10s)' - ); - - $this->assertEquals( - "1m 0s", - $this->lang->formatTimePeriod( 59.55 ), - 'formatTimePeriod() rounding (<60s)' - ); - - $this->assertEquals( - "2m 0s", - $this->lang->formatTimePeriod( 119.55 ), - 'formatTimePeriod() rounding (<1h)' - ); - - $this->assertEquals( - "1h 0m 0s", - $this->lang->formatTimePeriod( 3599.55 ), - 'formatTimePeriod() rounding (<1h)' - ); - - $this->assertEquals( - "2h 0m 0s", - $this->lang->formatTimePeriod( 7199.55 ), - 'formatTimePeriod() rounding (>=1h)' - ); - - $this->assertEquals( - "2h 0m", - $this->lang->formatTimePeriod( 7199.55, 'avoidseconds' ), - 'formatTimePeriod() rounding (>=1h), avoidseconds' - ); - - $this->assertEquals( - "2h 0m", - $this->lang->formatTimePeriod( 7199.55, 'avoidminutes' ), - 'formatTimePeriod() rounding (>=1h), avoidminutes' - ); - - $this->assertEquals( - "48h 0m", - $this->lang->formatTimePeriod( 172799.55, 'avoidseconds' ), - 'formatTimePeriod() rounding (=48h), avoidseconds' - ); - - $this->assertEquals( - "3d 0h", - $this->lang->formatTimePeriod( 259199.55, 'avoidminutes' ), - 'formatTimePeriod() rounding (>48h), avoidminutes' - ); - - $this->assertEquals( - "2d 1h 0m", - $this->lang->formatTimePeriod( 176399.55, 'avoidseconds' ), - 'formatTimePeriod() rounding (>48h), avoidseconds' - ); - - $this->assertEquals( - "2d 1h", - $this->lang->formatTimePeriod( 176399.55, 'avoidminutes' ), - 'formatTimePeriod() rounding (>48h), avoidminutes' - ); - - $this->assertEquals( - "3d 0h 0m", - $this->lang->formatTimePeriod( 259199.55, 'avoidseconds' ), - 'formatTimePeriod() rounding (>48h), avoidminutes' - ); - - $this->assertEquals( - "2d 0h 0m", - $this->lang->formatTimePeriod( 172801.55, 'avoidseconds' ), - 'formatTimePeriod() rounding, (>48h), avoidseconds' - ); - - $this->assertEquals( - "2d 1h 1m 1s", - $this->lang->formatTimePeriod( 176460.55 ), - 'formatTimePeriod() rounding, recursion, (>48h)' + + /** @dataProvider provideFormattableTimes */ + function testFormatTimePeriod( $seconds, $format, $expected, $desc ) { + $this->assertEquals( $expected, $this->lang->formatTimePeriod( $seconds, $format ), $desc ); + } + + function provideFormattableTimes() { + return array( + array( 9.45, array(), '9.5s', 'formatTimePeriod() rounding (<10s)' ), + array( 9.45, array( 'noabbrevs' => true ), '9.5 seconds', 'formatTimePeriod() rounding (<10s)' ), + array( 9.95, array(), '10s', 'formatTimePeriod() rounding (<10s)' ), + array( 9.95, array( 'noabbrevs' => true ), '10 seconds', 'formatTimePeriod() rounding (<10s)' ), + array( 59.55, array(), '1m 0s', 'formatTimePeriod() rounding (<60s)' ), + array( 59.55, array( 'noabbrevs' => true ), '1 minute 0 seconds', 'formatTimePeriod() rounding (<60s)' ), + array( 119.55, array(), '2m 0s', 'formatTimePeriod() rounding (<1h)' ), + array( 119.55, array( 'noabbrevs' => true ), '2 minutes 0 seconds', 'formatTimePeriod() rounding (<1h)' ), + array( 3599.55, array(), '1h 0m 0s', 'formatTimePeriod() rounding (<1h)' ), + array( 3599.55, array( 'noabbrevs' => true ), '1 hour 0 minutes 0 seconds', 'formatTimePeriod() rounding (<1h)' ), + array( 7199.55, array(), '2h 0m 0s', 'formatTimePeriod() rounding (>=1h)' ), + array( 7199.55, array( 'noabbrevs' => true ), '2 hours 0 minutes 0 seconds', 'formatTimePeriod() rounding (>=1h)' ), + array( 7199.55, 'avoidseconds', '2h 0m', 'formatTimePeriod() rounding (>=1h), avoidseconds' ), + array( 7199.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 hours 0 minutes', 'formatTimePeriod() rounding (>=1h), avoidseconds' ), + array( 7199.55, 'avoidminutes', '2h 0m', 'formatTimePeriod() rounding (>=1h), avoidminutes' ), + array( 7199.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '2 hours 0 minutes', 'formatTimePeriod() rounding (>=1h), avoidminutes' ), + array( 172799.55, 'avoidseconds', '48h 0m', 'formatTimePeriod() rounding (=48h), avoidseconds' ), + array( 172799.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '48 hours 0 minutes', 'formatTimePeriod() rounding (=48h), avoidseconds' ), + array( 259199.55, 'avoidminutes', '3d 0h', 'formatTimePeriod() rounding (>48h), avoidminutes' ), + array( 259199.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '3 days 0 hours', 'formatTimePeriod() rounding (>48h), avoidminutes' ), + array( 176399.55, 'avoidseconds', '2d 1h 0m', 'formatTimePeriod() rounding (>48h), avoidseconds' ), + array( 176399.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 days 1 hour 0 minutes', 'formatTimePeriod() rounding (>48h), avoidseconds' ), + array( 176399.55, 'avoidminutes', '2d 1h', 'formatTimePeriod() rounding (>48h), avoidminutes' ), + array( 176399.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '2 days 1 hour', 'formatTimePeriod() rounding (>48h), avoidminutes' ), + array( 259199.55, 'avoidseconds', '3d 0h 0m', 'formatTimePeriod() rounding (>48h), avoidseconds' ), + array( 259199.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '3 days 0 hours 0 minutes', 'formatTimePeriod() rounding (>48h), avoidseconds' ), + array( 172801.55, 'avoidseconds', '2d 0h 0m', 'formatTimePeriod() rounding, (>48h), avoidseconds' ), + array( 172801.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 days 0 hours 0 minutes', 'formatTimePeriod() rounding, (>48h), avoidseconds' ), + array( 176460.55, array(), '2d 1h 1m 1s', 'formatTimePeriod() rounding, recursion, (>48h)' ), + array( 176460.55, array( 'noabbrevs' => true ), '2 days 1 hour 1 minute 1 second', 'formatTimePeriod() rounding, recursion, (>48h)' ), ); + } function testTruncate() { @@ -243,4 +196,462 @@ class LanguageTest extends MediaWikiTestCase { array( 'Be-x-old', 'With extension (two dashes)' ), ); } + + /** + * @dataProvider provideSprintfDateSamples + */ + function testSprintfDate( $format, $ts, $expected, $msg ) { + $this->assertEquals( + $expected, + $this->lang->sprintfDate( $format, $ts ), + "sprintfDate('$format', '$ts'): $msg" + ); + } + /** + * bug 33454. sprintfDate should always use UTC. + * @dataProvider provideSprintfDateSamples + */ + function testSprintfDateTZ( $format, $ts, $expected, $msg ) { + $oldTZ = date_default_timezone_get(); + $res = date_default_timezone_set( 'Asia/Seoul' ); + if ( !$res ) { + $this->markTestSkipped( "Error setting Timezone" ); + } + + $this->assertEquals( + $expected, + $this->lang->sprintfDate( $format, $ts ), + "sprintfDate('$format', '$ts'): $msg" + ); + + date_default_timezone_set( $oldTZ ); + } + + function provideSprintfDateSamples() { + return array( + array( + 'xiY', + '20111212000000', + '1390', // note because we're testing English locale we get Latin-standard digits + 'Iranian calendar full year' + ), + array( + 'xiy', + '20111212000000', + '90', + 'Iranian calendar short year' + ), + array( + 'o', + '20120101235000', + '2011', + 'ISO 8601 (week) year' + ), + array( + 'W', + '20120101235000', + '52', + 'Week number' + ), + array( + 'W', + '20120102235000', + '1', + 'Week number' + ), + array( + 'o-\\WW-N', + '20091231235000', + '2009-W53-4', + 'leap week' + ), + // What follows is mostly copied from http://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time + array( + 'Y', + '20120102090705', + '2012', + 'Full year' + ), + array( + 'y', + '20120102090705', + '12', + '2 digit year' + ), + array( + 'L', + '20120102090705', + '1', + 'Leap year' + ), + array( + 'n', + '20120102090705', + '1', + 'Month index, not zero pad' + ), + array( + 'N', + '20120102090705', + '01', + 'Month index. Zero pad' + ), + array( + 'M', + '20120102090705', + 'Jan', + 'Month abbrev' + ), + array( + 'F', + '20120102090705', + 'January', + 'Full month' + ), + array( + 'xg', + '20120102090705', + 'January', + 'Genitive month name (same in EN)' + ), + array( + 'j', + '20120102090705', + '2', + 'Day of month (not zero pad)' + ), + array( + 'd', + '20120102090705', + '02', + 'Day of month (zero-pad)' + ), + array( + 'z', + '20120102090705', + '1', + 'Day of year (zero-indexed)' + ), + array( + 'D', + '20120102090705', + 'Mon', + 'Day of week (abbrev)' + ), + array( + 'l', + '20120102090705', + 'Monday', + 'Full day of week' + ), + array( + 'N', + '20120101090705', + '7', + 'Day of week (Mon=1, Sun=7)' + ), + array( + 'w', + '20120101090705', + '0', + 'Day of week (Sun=0, Sat=6)' + ), + array( + 'N', + '20120102090705', + '1', + 'Day of week' + ), + array( + 'a', + '20120102090705', + 'am', + 'am vs pm' + ), + array( + 'A', + '20120102120000', + 'PM', + 'AM vs PM' + ), + array( + 'a', + '20120102000000', + 'am', + 'AM vs PM' + ), + array( + 'g', + '20120102090705', + '9', + '12 hour, not Zero' + ), + array( + 'h', + '20120102090705', + '09', + '12 hour, zero padded' + ), + array( + 'G', + '20120102090705', + '9', + '24 hour, not zero' + ), + array( + 'H', + '20120102090705', + '09', + '24 hour, zero' + ), + array( + 'H', + '20120102110705', + '11', + '24 hour, zero' + ), + array( + 'i', + '20120102090705', + '07', + 'Minutes' + ), + array( + 's', + '20120102090705', + '05', + 'seconds' + ), + array( + 'U', + '20120102090705', + '1325495225', + 'unix time' + ), + array( + 't', + '20120102090705', + '31', + 'Days in current month' + ), + array( + 'c', + '20120102090705', + '2012-01-02T09:07:05+00:00', + 'ISO 8601 timestamp' + ), + array( + 'r', + '20120102090705', + 'Mon, 02 Jan 2012 09:07:05 +0000', + 'RFC 5322' + ), + array( + 'xmj xmF xmn xmY', + '20120102090705', + '7 Safar 2 1433', + 'Islamic' + ), + array( + 'xij xiF xin xiY', + '20120102090705', + '12 Dey 10 1390', + 'Iranian' + ), + array( + 'xjj xjF xjn xjY', + '20120102090705', + '7 Tevet 4 5772', + 'Hebrew' + ), + array( + 'xjt', + '20120102090705', + '29', + 'Hebrew number of days in month' + ), + array( + 'xjx', + '20120102090705', + 'Tevet', + 'Hebrew genitive month name (No difference in EN)' + ), + array( + 'xkY', + '20120102090705', + '2555', + 'Thai year' + ), + array( + 'xoY', + '20120102090705', + '101', + 'Minguo' + ), + array( + 'xtY', + '20120102090705', + '平成24', + 'nengo' + ), + array( + 'xrxkYY', + '20120102090705', + 'MMDLV2012', + 'Roman numerals' + ), + array( + 'xhxjYY', + '20120102090705', + 'ה\'תשע"ב2012', + 'Hebrew numberals' + ), + array( + 'xnY', + '20120102090705', + '2012', + 'Raw numerals (doesn\'t mean much in EN)' + ), + array( + '[[Y "(yea"\\r)]] \\"xx\\"', + '20120102090705', + '[[2012 (year)]] "x"', + 'Various escaping' + ), + + ); + } + + /** + * @dataProvider provideFormatSizes + */ + function testFormatSize( $size, $expected, $msg ) { + $this->assertEquals( + $expected, + $this->lang->formatSize( $size ), + "formatSize('$size'): $msg" + ); + } + + function provideFormatSizes() { + return array( + array( + 0, + "0 B", + "Zero bytes" + ), + array( + 1024, + "1 KB", + "1 kilobyte" + ), + array( + 1024 * 1024, + "1 MB", + "1,024 megabytes" + ), + array( + 1024 * 1024 * 1024, + "1 GB", + "1 gigabytes" + ), + array( + pow( 1024, 4 ), + "1 TB", + "1 terabyte" + ), + array( + pow( 1024, 5 ), + "1 PB", + "1 petabyte" + ), + array( + pow( 1024, 6 ), + "1 EB", + "1,024 exabyte" + ), + array( + pow( 1024, 7 ), + "1 ZB", + "1 zetabyte" + ), + array( + pow( 1024, 8 ), + "1 YB", + "1 yottabyte" + ), + // How big!? THIS BIG! + ); + } + + /** + * @dataProvider provideFormatBitrate + */ + function testFormatBitrate( $bps, $expected, $msg ) { + $this->assertEquals( + $expected, + $this->lang->formatBitrate( $bps ), + "formatBitrate('$bps'): $msg" + ); + } + + function provideFormatBitrate() { + return array( + array( + 0, + "0bps", + "0 bits per second" + ), + array( + 999, + "999bps", + "999 bits per second" + ), + array( + 1000, + "1kbps", + "1 kilobit per second" + ), + array( + 1000 * 1000, + "1Mbps", + "1 megabit per second" + ), + array( + pow( 10, 9 ), + "1Gbps", + "1 gigabit per second" + ), + array( + pow( 10, 12 ), + "1Tbps", + "1 terabit per second" + ), + array( + pow( 10, 15 ), + "1Pbps", + "1 petabit per second" + ), + array( + pow( 10, 18 ), + "1Ebps", + "1 exabit per second" + ), + array( + pow( 10, 21 ), + "1Zbps", + "1 zetabit per second" + ), + array( + pow( 10, 24 ), + "1Ybps", + "1 yottabit per second" + ), + array( + pow( 10, 27 ), + "1,000Ybps", + "1,000 yottabits per second" + ), + ); + } } diff --git a/tests/phpunit/languages/LanguageTiTest.php b/tests/phpunit/languages/LanguageTiTest.php new file mode 100644 index 00000000..4bfaa009 --- /dev/null +++ b/tests/phpunit/languages/LanguageTiTest.php @@ -0,0 +1,32 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageTi.php */ +class LanguageTiTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Ti' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'many' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'many', 2 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageTlTest.php b/tests/phpunit/languages/LanguageTlTest.php new file mode 100644 index 00000000..a1facd14 --- /dev/null +++ b/tests/phpunit/languages/LanguageTlTest.php @@ -0,0 +1,32 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageTl.php */ +class LanguageTlTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Tl' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'many' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'many', 2 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageTrTest.php b/tests/phpunit/languages/LanguageTrTest.php index d2a5ff36..bda4c9d9 100644 --- a/tests/phpunit/languages/LanguageTrTest.php +++ b/tests/phpunit/languages/LanguageTrTest.php @@ -1,7 +1,7 @@ <?php /** - * @author Ashar Voultoiz - * @copyright Copyright © 2011, Ashar Voultoiz + * @author Antoine Musso + * @copyright Copyright © 2011, Antoine Musso * @file */ @@ -18,7 +18,10 @@ class LanguageTrTest extends MediaWikiTestCase { /** * See @bug 28040 - * Credits to #wikipedia-tr users berm, []LuCkY[] and Emperyan + * Credits to irc://irc.freenode.net/wikipedia-tr users: + * - berm + * - []LuCkY[] + * - Emperyan * @see http://en.wikipedia.org/wiki/Dotted_and_dotless_I * @dataProvider provideDottedAndDotlessI */ diff --git a/tests/phpunit/languages/LanguageUkTest.php b/tests/phpunit/languages/LanguageUkTest.php new file mode 100644 index 00000000..60fafb0d --- /dev/null +++ b/tests/phpunit/languages/LanguageUkTest.php @@ -0,0 +1,54 @@ +<?php +/** + * @author Amir E. Aharoni + * based on LanguageBe_tarask.php + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageUk.php */ +class LanguageUkTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Uk' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providePluralFourForms */ + function testPluralFourForms( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providePluralFourForms() { + return array ( + array( 'one', 1 ), + array( 'many', 11 ), + array( 'one', 91 ), + array( 'one', 121 ), + array( 'few', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), + array( 'few', 334 ), + array( 'many', 5 ), + array( 'many', 15 ), + array( 'many', 120 ), + ); + } + /** @dataProvider providePluralTwoForms */ + function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'several' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + function providePluralTwoForms() { + return array ( + array( 'one', 1 ), + array( 'several', 11 ), + array( 'several', 91 ), + array( 'several', 121 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageWaTest.php b/tests/phpunit/languages/LanguageWaTest.php new file mode 100644 index 00000000..172f19b9 --- /dev/null +++ b/tests/phpunit/languages/LanguageWaTest.php @@ -0,0 +1,32 @@ +<?php +/** + * @author Amir E. Aharoni + * @copyright Copyright © 2012, Amir E. Aharoni + * @file + */ + +/** Tests for MediaWiki languages/classes/LanguageWa.php */ +class LanguageWaTest extends MediaWikiTestCase { + private $lang; + + function setUp() { + $this->lang = Language::factory( 'Wa' ); + } + function tearDown() { + unset( $this->lang ); + } + + /** @dataProvider providerPlural */ + function testPlural( $result, $value ) { + $forms = array( 'one', 'many' ); + $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + } + + function providerPlural() { + return array ( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'many', 2 ), + ); + } +} diff --git a/tests/phpunit/phpunit.php b/tests/phpunit/phpunit.php index 39cccf80..92eeffa2 100644 --- a/tests/phpunit/phpunit.php +++ b/tests/phpunit/phpunit.php @@ -46,8 +46,8 @@ require( RUN_MAINTENANCE_IF_MAIN ); if( !in_array( '--configuration', $_SERVER['argv'] ) ) { //Hack to eliminate the need to use the Makefile (which sucks ATM) - $_SERVER['argv'][] = '--configuration'; - $_SERVER['argv'][] = $IP . '/tests/phpunit/suite.xml'; + array_splice( $_SERVER['argv'], 1, 0, + array( '--configuration', $IP . '/tests/phpunit/suite.xml' ) ); } require_once( 'PHPUnit/Runner/Version.php' ); diff --git a/tests/phpunit/skins/SideBarTest.php b/tests/phpunit/skins/SideBarTest.php index bf79e760..912d7602 100644 --- a/tests/phpunit/skins/SideBarTest.php +++ b/tests/phpunit/skins/SideBarTest.php @@ -37,6 +37,7 @@ class SideBarTest extends MediaWikiLangTestCase { parent::setUp(); $this->initMessagesHref(); $this->skin = new SkinTemplate(); + $this->skin->getContext()->setLanguage( Language::factory( 'en' ) ); } function tearDown() { parent::tearDown(); @@ -106,7 +107,7 @@ class SideBarTest extends MediaWikiLangTestCase { } /** - * bug 33321 + * bug 33321 - Make sure there's a | after transforming. * @group Database */ function testTrickyPipe() { @@ -168,7 +169,7 @@ class SideBarTest extends MediaWikiLangTestCase { } /** - * Test wgNoFollowLinks in sidebar + * Test $wgNoFollowLinks in sidebar */ function testRespectWgnofollowlinks() { global $wgNoFollowLinks; @@ -177,7 +178,7 @@ class SideBarTest extends MediaWikiLangTestCase { $attribs = $this->getAttribs(); $this->assertArrayNotHasKey( 'rel', $attribs, - 'External URL in sidebar do not have rel=nofollow when wgNoFollowLinks = false' + 'External URL in sidebar do not have rel=nofollow when $wgNoFollowLinks = false' ); // Restore global @@ -185,7 +186,7 @@ class SideBarTest extends MediaWikiLangTestCase { } /** - * Test wgExternaLinkTarget in sidebar + * Test $wgExternaLinkTarget in sidebar */ function testRespectExternallinktarget() { global $wgExternalLinkTarget; diff --git a/tests/phpunit/suite.xml b/tests/phpunit/suite.xml index e6649beb..1227a17a 100644 --- a/tests/phpunit/suite.xml +++ b/tests/phpunit/suite.xml @@ -2,35 +2,42 @@ <!-- colors don't work on Windows! --> <phpunit bootstrap="./bootstrap.php" - colors="false" + colors="true" backupGlobals="false" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" stopOnFailure="false" + timeoutForSmallTests="2" + timeoutForMediumTests="10" + timeoutForLargeTests="60" strict="true" verbose="true"> <testsuites> <testsuite name="includes"> - <directory>./includes</directory> + <directory>includes</directory> </testsuite> <testsuite name="languages"> - <directory>./languages</directory> + <directory>languages</directory> </testsuite> <testsuite name="skins"> - <directory>./skins</directory> + <directory>skins</directory> + </testsuite> + <testsuite name="structure"> + <file>StructureTest.php</file> </testsuite> <testsuite name="uploadfromurl"> - <file>./suites/UploadFromUrlTestSuite.php</file> + <file>suites/UploadFromUrlTestSuite.php</file> </testsuite> <testsuite name="extensions"> - <file>./suites/ExtensionsTestSuite.php</file> + <file>suites/ExtensionsTestSuite.php</file> </testsuite> </testsuites> <groups> <exclude> <group>Utility</group> <group>Broken</group> + <group>ParserFuzz</group> <group>Stub</group> </exclude> </groups> diff --git a/tests/phpunit/suites/UploadFromUrlTestSuite.php b/tests/phpunit/suites/UploadFromUrlTestSuite.php index 9b666825..6779ad47 100644 --- a/tests/phpunit/suites/UploadFromUrlTestSuite.php +++ b/tests/phpunit/suites/UploadFromUrlTestSuite.php @@ -3,6 +3,8 @@ require_once( dirname( dirname( __FILE__ ) ) . '/includes/upload/UploadFromUrlTest.php' ); class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { + public $savedGlobals = array(); + public static function addTables( &$tables ) { $tables[] = 'user_properties'; $tables[] = 'filearchive'; @@ -14,34 +16,49 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { } function setUp() { - global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgDeferredUpdateList, - $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache, - $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo, - $parserMemc, $wgThumbnailScriptPath, $wgScriptPath, - $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath; - - $wgScript = '/index.php'; - $wgScriptPath = '/'; - $wgArticlePath = '/wiki/$1'; - $wgStyleSheetPath = '/skins'; - $wgStylePath = '/skins'; - $wgThumbnailScriptPath = false; - $wgLocalFileRepo = array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'directory' => wfTempDir() . '/test-repo', - 'url' => 'http://example.com/images', - 'deletedDir' => wfTempDir() . '/test-repo/delete', - 'hashLevels' => 2, + global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, + $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache, + $wgNamespaceAliases, $wgNamespaceProtection, $parserMemc; + + $tmpGlobals = array(); + + $tmpGlobals['wgScript'] = '/index.php'; + $tmpGlobals['wgScriptPath'] = '/'; + $tmpGlobals['wgArticlePath'] = '/wiki/$1'; + $tmpGlobals['wgStyleSheetPath'] = '/skins'; + $tmpGlobals['wgStylePath'] = '/skins'; + $tmpGlobals['wgThumbnailScriptPath'] = false; + $tmpGlobals['wgLocalFileRepo'] = array( + 'class' => 'LocalRepo', + 'name' => 'local', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, 'transformVia404' => false, + 'backend' => new FSFileBackend( array( + 'name' => 'local-backend', + 'lockManager' => 'fsLockManager', + 'containerPaths' => array( + 'local-public' => wfTempDir() . '/test-repo/public', + 'local-thumb' => wfTempDir() . '/test-repo/thumb', + 'local-temp' => wfTempDir() . '/test-repo/temp', + 'local-deleted' => wfTempDir() . '/test-repo/delete', + ) + ) ), ); + foreach ( $tmpGlobals as $var => $val ) { + if ( array_key_exists( $var, $GLOBALS ) ) { + $this->savedGlobals[$var] = $GLOBALS[$var]; + } + $GLOBALS[$var] = $val; + } + $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface'; $wgNamespaceAliases['Image'] = NS_FILE; $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; $wgEnableParserCache = false; - $wgDeferredUpdateList = array(); + DeferredUpdates::clearPendingUpdates(); $wgMemc = wfGetMainCache(); $messageMemc = wfGetMessageCacheStorage(); $parserMemc = wfGetParserCacheStorage(); @@ -49,18 +66,27 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { // $wgContLang = new StubContLang; $wgUser = new User; $context = new RequestContext; - $wgLang = $context->getLang(); + $wgLang = $context->getLanguage(); $wgOut = $context->getOutput(); $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) ); - $wgRequest = new WebRequest; + $wgRequest = $context->getRequest(); if ( $wgStyleDirectory === false ) { $wgStyleDirectory = "$IP/skins"; } + RepoGroup::destroySingleton(); + FileBackendGroup::destroySingleton(); } public function tearDown() { + foreach ( $this->savedGlobals as $var => $val ) { + $GLOBALS[$var] = $val; + } + // Restore backends + RepoGroup::destroySingleton(); + FileBackendGroup::destroySingleton(); + $this->teardownUploadDir( $this->uploadDir ); } @@ -159,10 +185,10 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { return $dir; } - wfMkdirParents( $dir . '/3/3a' ); + wfMkdirParents( $dir . '/3/3a', null, __METHOD__ ); copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" ); - wfMkdirParents( $dir . '/0/09' ); + wfMkdirParents( $dir . '/0/09', null, __METHOD__ ); copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" ); return $dir; diff --git a/tests/qunit/QUnitTestResources.php b/tests/qunit/QUnitTestResources.php new file mode 100644 index 00000000..670e3d11 --- /dev/null +++ b/tests/qunit/QUnitTestResources.php @@ -0,0 +1,52 @@ +<?php + +return array( + + /* Test suites for MediaWiki core modules */ + + 'mediawiki.tests.qunit.suites' => array( + 'scripts' => array( + 'tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js', + 'tests/qunit/suites/resources/jquery/jquery.byteLength.test.js', + 'tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js', + 'tests/qunit/suites/resources/jquery/jquery.client.test.js', + 'tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js', + 'tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js', + 'tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js', + 'tests/qunit/suites/resources/jquery/jquery.highlightText.test.js', + 'tests/qunit/suites/resources/jquery/jquery.localize.test.js', + 'tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js', + 'tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js', + 'tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js', + 'tests/qunit/suites/resources/jquery/jquery.textSelection.test.js', + 'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js', + 'tests/qunit/suites/resources/mediawiki/mediawiki.test.js', + 'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js', + 'tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js', + 'tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js', + 'tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js', + "tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js", + ), + 'dependencies' => array( + 'jquery.autoEllipsis', + 'jquery.byteLength', + 'jquery.byteLimit', + 'jquery.client', + 'jquery.colorUtil', + 'jquery.delayedBind', + 'jquery.getAttrs', + 'jquery.highlightText', + 'jquery.localize', + 'jquery.mwExtension', + 'jquery.tabIndex', + 'jquery.tablesorter', + 'jquery.textSelection', + 'mediawiki', + 'mediawiki.Title', + 'mediawiki.user', + 'mediawiki.util', + 'mediawiki.special.recentchanges', + 'mediawiki.jqueryMsg', + ), + ) +); diff --git a/tests/qunit/data/qunitOkCall.js b/tests/qunit/data/qunitOkCall.js new file mode 100644 index 00000000..2fb6e01d --- /dev/null +++ b/tests/qunit/data/qunitOkCall.js @@ -0,0 +1,2 @@ +start(); +ok( true, 'Successfully loaded!'); diff --git a/tests/qunit/data/testrunner.js b/tests/qunit/data/testrunner.js index dbfe9fad..fdd3116b 100644 --- a/tests/qunit/data/testrunner.js +++ b/tests/qunit/data/testrunner.js @@ -1,36 +1,61 @@ -( function( $ ) { +( function ( $, mw, QUnit, undefined ) { +"use strict"; + +var mwTestIgnore, mwTester, addons; /** * Add bogus to url to prevent IE crazy caching * - * @param value {String} a relative path (eg. 'data/defineTestCallback.js' or 'data/test.php?foo=bar') + * @param value {String} a relative path (eg. 'data/defineTestCallback.js' + * or 'data/test.php?foo=bar'). * @return {String} Such as 'data/defineTestCallback.js?131031765087663960' */ -QUnit.fixurl = function(value) { - return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000); +QUnit.fixurl = function (value) { + return value + (/\?/.test( value ) ? '&' : '?') + + String( new Date().getTime() ) + + String( parseInt( Math.random()*100000, 10 ) ); }; /** + * Configuration + */ +QUnit.config.testTimeout = 5000; + +/** + * MediaWiki debug mode + */ +QUnit.config.urlConfig.push( 'debug' ); + +/** * Load TestSwarm agent */ if ( QUnit.urlParams.swarmURL ) { - document.write("<scr" + "ipt src='" + QUnit.fixurl( 'data/testwarm.inject.js' ) + "'></scr" + "ipt>"); + document.write( "<scr" + "ipt src='" + QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + + '/tests/qunit/data/testwarm.inject.js' ) + "'></scr" + "ipt>" ); } /** - * Load completenesstest + * CompletenessTest */ +// Adds toggle checkbox to header +QUnit.config.urlConfig.push( 'completenesstest' ); + +// Initiate when enabled if ( QUnit.urlParams.completenesstest ) { // Return true to ignore - var mwTestIgnore = function( val, tester, funcPath ) { + mwTestIgnore = function ( val, tester, funcPath ) { // Don't record methods of the properties of constructors, // to avoid getting into a loop (prototype.constructor.prototype..). // Since we're therefor skipping any injection for // "new mw.Foo()", manually set it to true here. if ( val instanceof mw.Map ) { - tester.methodCallTracker['Map'] = true; + tester.methodCallTracker.Map = true; + return true; + } + if ( val instanceof mw.Title ) { + tester.methodCallTracker.Title = true; return true; } @@ -42,42 +67,113 @@ if ( QUnit.urlParams.completenesstest ) { return false; }; - var mwTester = new CompletenessTest( mw, mwTestIgnore ); + mwTester = new CompletenessTest( mw, mwTestIgnore ); } /** + * Test environment recommended for all QUnit test modules + */ +// Whether to log environment changes to the console +QUnit.config.urlConfig.push( 'mwlogenv' ); + +/** + * Reset mw.config to a fresh copy of the live config for each test(); + * @param override {Object} [optional] + * @example: + * <code> + * module( .., newMwEnvironment() ); + * + * test( .., function () { + * mw.config.set( 'foo', 'bar' ); // just for this test + * } ); + * + * test( .., function () { + * mw.config.get( 'foo' ); // doesn't exist + * } ); + * + * + * module( .., newMwEnvironment({ quux: 'corge' }) ); + * + * test( .., function () { + * mw.config.get( 'quux' ); // "corge" + * mw.config.set( 'quux', "grault" ); + * } ); + * + * test( .., function () { + * mw.config.get( 'quux' ); // "corge" + * } ); + * </code> + */ +QUnit.newMwEnvironment = ( function () { + var liveConfig, freshConfigCopy, log; + + liveConfig = mw.config.values; + + freshConfigCopy = function ( custom ) { + // "deep=true" is important here. + // Otherwise we just create a new object with values referring to live config. + // e.g. mw.config.set( 'wgFileExtensions', [] ) would not effect liveConfig, + // but mw.config.get( 'wgFileExtensions' ).push( 'png' ) would as the array + // was passed by reference in $.extend's loop. + return $.extend({}, liveConfig, custom, /*deep=*/true ); + }; + + log = QUnit.urlParams.mwlogenv ? mw.log : function () {}; + + return function ( override ) { + override = override || {}; + + return { + setup: function () { + log( 'MwEnvironment> SETUP for "' + QUnit.config.current.module + + ': ' + QUnit.config.current.testName + '"' ); + // Greetings, mock configuration! + mw.config.values = freshConfigCopy( override ); + }, + + teardown: function () { + log( 'MwEnvironment> TEARDOWN for "' + QUnit.config.current.module + + ': ' + QUnit.config.current.testName + '"' ); + // Farewell, mock configuration! + mw.config.values = liveConfig; + } + }; + }; +}() ); + +/** * Add-on assertion helpers */ // Define the add-ons -var addons = { +addons = { // Expect boolean true - assertTrue: function( actual, message ) { + assertTrue: function ( actual, message ) { strictEqual( actual, true, message ); }, // Expect boolean false - assertFalse: function( actual, message ) { + assertFalse: function ( actual, message ) { strictEqual( actual, false, message ); }, // Expect numerical value less than X - lt: function( actual, expected, message ) { + lt: function ( actual, expected, message ) { QUnit.push( actual < expected, actual, 'less than ' + expected, message ); }, // Expect numerical value less than or equal to X - ltOrEq: function( actual, expected, message ) { + ltOrEq: function ( actual, expected, message ) { QUnit.push( actual <= expected, actual, 'less than or equal to ' + expected, message ); }, // Expect numerical value greater than X - gt: function( actual, expected, message ) { + gt: function ( actual, expected, message ) { QUnit.push( actual > expected, actual, 'greater than ' + expected, message ); }, // Expect numerical value greater than or equal to X - gtOrEq: function( actual, expected, message ) { + gtOrEq: function ( actual, expected, message ) { QUnit.push( actual >= expected, actual, 'greater than or equal to ' + expected, message ); }, @@ -89,4 +185,4 @@ var addons = { $.extend( QUnit, addons ); $.extend( window, addons ); -})( jQuery ); +})( jQuery, mediaWiki, QUnit ); diff --git a/tests/qunit/index.html b/tests/qunit/index.html index f748b87f..ef7ff8de 100644 --- a/tests/qunit/index.html +++ b/tests/qunit/index.html @@ -9,6 +9,43 @@ <script> function startUp(){ mw.config = new mw.Map( false ); + + /** + * Simulate an average mw.config context + */ + /* StartUp module */ + mw.config.set({"wgLoadScript": "/mw/trunk/phase3/load.php", "debug": true, "skin": "vector", "stylepath": "/mw/trunk/phase3/skins", "wgUrlProtocols": "http\\:\\/\\/|https\\:\\/\\/|ftp\\:\\/\\/|irc\\:\\/\\/|ircs\\:\\/\\/|gopher\\:\\/\\/|telnet\\:\\/\\/|nntp\\:\\/\\/|worldwind\\:\\/\\/|mailto\\:|news\\:|svn\\:\\/\\/|git\\:\\/\\/|mms\\:\\/\\/|\\/\\/", "wgArticlePath": "/mw/trunk/phase3/index.php/$1", "wgScriptPath": "/mw/trunk/phase3", "wgScriptExtension": ".php", "wgScript": "/mw/trunk/phase3/index.php", "wgVariantArticlePath": false, "wgActionPaths": [], "wgServer": "http://localhost", "wgUserLanguage": "en", "wgContentLanguage": "en", "wgVersion": "1.19alpha", "wgEnableAPI": true, "wgEnableWriteAPI": true, "wgDefaultDateFormat": "dmy", "wgMonthNames": ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], "wgMonthNamesShort": ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], "wgMainPageTitle": "Main Page", "wgFormattedNamespaces": {"-2": "Media", "-1": "Special", "0": "", "1": "Talk", "2": "User", "3": "User talk", "4": "Testopedia", "5": "Testopedia talk", "6": "File", "7": "File talk", "8": "MediaWiki", "9": "MediaWiki talk", "10": "Template", "11": "Template talk", "12": "Help", "13": "Help talk", "14": "Category", "15": "Category talk"}, "wgNamespaceIds": {"media": -2, "special": -1, "": 0, "talk": 1, "user": 2, "user_talk": 3, "testopedia": 4, "testopedia_talk": 5, "file": 6, "file_talk": 7, "mediawiki": 8, "mediawiki_talk": 9, "template": 10, "template_talk": 11, "help": 12, "help_talk": 13, "category": 14, "category_talk": 15, "image": 6, "image_talk": 7, "project": 4, "project_talk": 5}, "wgSiteName": "Testopedia", "wgFileExtensions": ["png", "gif", "jpg", "jpeg"], "wgDBname": "mediawiki", "wgFileCanRotate": true, "wgAvailableSkins": {"chick": "Chick", "cologneblue": "CologneBlue", "modern": "Modern", "monobook": "MonoBook", "myskin": "MySkin", "nostalgia": "Nostalgia", "simple": "Simple", "standard": "Standard", "vector": "Vector"}, "wgExtensionAssetsPath": "/mw/trunk/phase3/extensions", "wgCookiePrefix": "mediawiki", "wgResourceLoaderMaxQueryLength": -1, "wgCaseSensitiveNamespaces": []}); + + /* WikiPage specific */ + mw.config.set({"wgCanonicalNamespace": "", "wgCanonicalSpecialPageName": false, "wgNamespaceNumber": 0, "wgPageName": "Sandbox", "wgTitle": "Sandbox", "wgCurRevisionId": 486, "wgArticleId": 84, "wgIsArticle": true, "wgAction": "view", "wgUserName": null, "wgUserGroups": ["*"], "wgCategories": [], "wgBreakFrames": false, "wgPageContentLanguage": "en", "wgSeparatorTransformTable": ["", ""], "wgDigitTransformTable": ["", ""], "wgRestrictionEdit": [], "wgRestrictionMove": [], "wgRedirectedFrom": "Sandbox"}); + + /** + * Fix wgScriptPath and the like to the real thing, + * instead of fake ones (for access to /tests/qunit/data/) + */ + + // Regular expression to extract the path for the QUnit tests + // Takes into account that tests could be run from a file:// URL + // by excluding the 'index.html' part from the URL + var rePath = /(?:[^#\?](?!index.html))*\/?/; + + // Extract path to /tests/qunit/ + var qunitTestsPath = rePath.exec( location.pathname )[0]; + + // Traverse up to script path + var pathParts = qunitTestsPath.split( '/' ); + pathParts.pop(); pathParts.pop(); pathParts.pop(); + var scriptPath = pathParts.join( '/' ); + + mw.config.set({ + "wgServer": location.protocol + '//' + location.host, + "wgScriptPath": scriptPath, + "wgLoadScript": scriptPath + '/load.php', + "stylepath": scriptPath + '/skins', + "wgArticlePath": scriptPath + '/index.php/$1', + "wgScript": scriptPath + '/index.php', + "wgExtensionAssetsPath": scriptPath + '/extensions' + }); } </script> @@ -18,15 +55,15 @@ <!-- MW: mediawiki.page.startup --> <script src="../../resources/jquery/jquery.client.js"></script> + <script src="../../resources/mediawiki/mediawiki.util.js"></script> <script src="../../resources/mediawiki.page/mediawiki.page.startup.js"></script> - <!-- MW: mediawiki.user|mediawiki.util|mediawiki.page.ready --> + <!-- MW: mediawiki.user|mediawiki.page.ready --> <script src="../../resources/jquery/jquery.cookie.js"></script> <script src="../../resources/mediawiki/mediawiki.user.js"></script> <script src="../../resources/jquery/jquery.messageBox.js"></script> - <script src="../../resources/jquery/jquery.mwPrototypes.js"></script> - <script src="../../resources/mediawiki/mediawiki.util.js"></script> + <script src="../../resources/jquery/jquery.mwExtension.js"></script> <script src="../../resources/jquery/jquery.checkboxShiftClick.js"></script> <script src="../../resources/jquery/jquery.makeCollapsible.js"></script> @@ -45,9 +82,14 @@ <script src="../../resources/jquery/jquery.colorUtil.js"></script> <script src="../../resources/jquery/jquery.delayedBind.js"></script> <script src="../../resources/jquery/jquery.getAttrs.js"></script> + <script src="../../resources/jquery/jquery.highlightText.js"></script> <script src="../../resources/jquery/jquery.localize.js"></script> <script src="../../resources/jquery/jquery.tabIndex.js"></script> <script src="../../resources/jquery/jquery.tablesorter.js"></script> + <script src="../../resources/jquery/jquery.textSelection.js"></script> + <script src="../../resources/mediawiki/mediawiki.Title.js"></script> + <script src="../../resources/mediawiki.language/mediawiki.language.js"></script> + <script src="../../resources/mediawiki/mediawiki.jqueryMsg.js"></script> <script src="../../resources/mediawiki.special/mediawiki.special.js"></script> <script src="../../resources/mediawiki.special/mediawiki.special.recentchanges.js"></script> @@ -59,23 +101,27 @@ <!-- QUnit: Load test suites (maintain the same order as above please) --> <script src="suites/resources/mediawiki/mediawiki.jscompat.test.js"></script> - <script src="suites/resources/mediawiki/mediawiki.js"></script> - <script src="suites/resources/mediawiki/mediawiki.user.js"></script> + <script src="suites/resources/mediawiki/mediawiki.test.js"></script> + <script src="suites/resources/mediawiki/mediawiki.user.test.js"></script> - <script src="suites/resources/jquery/jquery.client.js"></script> - <script src="suites/resources/jquery/jquery.mwPrototypes.js"></script> - <script src="suites/resources/mediawiki/mediawiki.util.js"></script> + <script src="suites/resources/jquery/jquery.client.test.js"></script> + <script src="suites/resources/jquery/jquery.mwExtension.test.js"></script> + <script src="suites/resources/mediawiki/mediawiki.util.test.js"></script> - <script src="suites/resources/jquery/jquery.autoEllipsis.js"></script> - <script src="suites/resources/jquery/jquery.byteLength.js"></script> - <script src="suites/resources/jquery/jquery.byteLimit.js"></script> - <script src="suites/resources/jquery/jquery.colorUtil.js"></script> + <script src="suites/resources/jquery/jquery.autoEllipsis.test.js"></script> + <script src="suites/resources/jquery/jquery.byteLength.test.js"></script> + <script src="suites/resources/jquery/jquery.byteLimit.test.js"></script> + <script src="suites/resources/jquery/jquery.colorUtil.test.js"></script> <script src="suites/resources/jquery/jquery.delayedBind.test.js"></script> - <script src="suites/resources/jquery/jquery.getAttrs.js"></script> - <script src="suites/resources/jquery/jquery.localize.js"></script> - <script src="suites/resources/jquery/jquery.tabIndex.js"></script> + <script src="suites/resources/jquery/jquery.getAttrs.test.js"></script> + <script src="suites/resources/jquery/jquery.highlightText.test.js"></script> + <script src="suites/resources/jquery/jquery.localize.test.js"></script> + <script src="suites/resources/jquery/jquery.tabIndex.test.js"></script> <script src="suites/resources/jquery/jquery.tablesorter.test.js" charset="UTF-8"></script> - <script src="suites/resources/mediawiki.special/mediawiki.special.recentchanges.js"></script> + <script src="suites/resources/jquery/jquery.textSelection.test.js" charset="UTF-8"></script> + <script src="suites/resources/mediawiki/mediawiki.Title.test.js"></script> + <script src="suites/resources/mediawiki/mediawiki.jqueryMsg.test.js"></script> + <script src="suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js"></script> </head> <body> <h1 id="qunit-header">MediaWiki JavaScript Test Suite</h1> @@ -85,6 +131,7 @@ </div> <h2 id="qunit-userAgent"></h2> <ol id="qunit-tests"></ol> + <div id="qunit-fixture"></div> <!-- Scripts inserting stuff here shall remove it themselfs! --> <div id="content"></div> diff --git a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js index caf5a6f1..6e371384 100644 --- a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js +++ b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js @@ -1,4 +1,4 @@ -module( 'jquery.autoEllipsis.js' ); +module( 'jquery.autoEllipsis', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect(1); @@ -6,8 +6,8 @@ test( '-- Initial check', function() { }); function createWrappedDiv( text, width ) { - var $wrapper = $( '<div />' ).css( 'width', width ); - var $div = $( '<div />' ).text( text ); + var $wrapper = $( '<div>' ).css( 'width', width ); + var $div = $( '<div>' ).text( text ); $wrapper.append( $div ); return $wrapper; } @@ -26,7 +26,7 @@ test( 'Position right', function() { // We need this thing to be visible, so append it to the DOM var origText = 'This is a really long random string and there is no way it fits in 100 pixels.'; var $wrapper = createWrappedDiv( origText, '100px' ); - $( 'body' ).append( $wrapper ); + $( '#qunit-fixture' ).append( $wrapper ); $wrapper.autoEllipsis( { position: 'right' } ); // Verify that, and only one, span element was created @@ -47,12 +47,9 @@ test( 'Position right', function() { // Put this text in the span and verify it doesn't fit $span.text( spanTextNew ); // In IE6 width works like min-width, allow IE6's width to be "equal to" - if ( $.browser.msie && Number( $.browser.version ) == 6 ) { + if ( $.browser.msie && Number( $.browser.version ) === 6 ) { gtOrEq( $span.width(), $span.parent().width(), 'Fit is maximal (adding two characters makes it not fit any more) - IE6: Maybe equal to as well due to width behaving like min-width in IE6' ); } else { gt( $span.width(), $span.parent().width(), 'Fit is maximal (adding two characters makes it not fit any more)' ); } - - // Clean up - $wrapper.remove(); }); diff --git a/tests/qunit/suites/resources/jquery/jquery.byteLength.js b/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js index f82fda27..15fac691 100644 --- a/tests/qunit/suites/resources/jquery/jquery.byteLength.js +++ b/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js @@ -1,4 +1,4 @@ -module( 'jquery.byteLength.js' ); +module( 'jquery.byteLength', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect(1); @@ -25,7 +25,7 @@ test( 'Simple text', function() { test( 'Special text', window.foo = function() { expect(5); - // http://en.wikipedia.org/wiki/UTF-8 + // http://en.wikipedia.org/wiki/UTF-8 var U_0024 = '\u0024', U_00A2 = '\u00A2', U_20AC = '\u20AC', diff --git a/tests/qunit/suites/resources/jquery/jquery.byteLimit.js b/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js index 461ea49b..3346c2d5 100644 --- a/tests/qunit/suites/resources/jquery/jquery.byteLimit.js +++ b/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js @@ -1,4 +1,6 @@ -module( 'jquery.byteLimit.js' ); +( function () { + +module( 'jquery.byteLimit', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect(1); @@ -23,46 +25,47 @@ $.addChars = function( $input, charstr ) { } } }; -var blti = 0; + /** * Test factory for $.fn.byteLimit * * @param $input {jQuery} jQuery object in an input element - * @param useLimit {Boolean} Wether a limit should apply at all + * @param hasLimit {Boolean} Wether a limit should apply at all * @param limit {Number} Limit (if used) otherwise undefined - * The limit should be less than 20 (the sample data's length) + * The limit should be less than 20 (the sample data's length) */ var byteLimitTest = function( options ) { var opt = $.extend({ description: '', $input: null, sample: '', - useLimit: false, - expected: 0, + hasLimit: false, + expected: '', limit: null }, options); - var i = blti++; test( opt.description, function() { - opt.$input.appendTo( 'body' ); - + opt.$input.appendTo( '#qunit-fixture' ); + // Simulate pressing keys for each of the sample characters $.addChars( opt.$input, opt.sample ); - var newVal = opt.$input.val(); - - if ( opt.useLimit ) { - expect(2); - + var rawVal = opt.$input.val(), + fn = opt.$input.data( 'byteLimit-callback' ), + newVal = $.isFunction( fn ) ? fn( rawVal ) : rawVal; + + if ( opt.hasLimit ) { + expect(3); + ltOrEq( $.byteLength( newVal ), opt.limit, 'Prevent keypresses after byteLimit was reached, length never exceeded the limit' ); - equal( $.byteLength( newVal ), opt.expected, 'Not preventing keypresses too early, length has reached the expected length' ); - + equal( $.byteLength( rawVal ), $.byteLength( opt.expected ), 'Not preventing keypresses too early, length has reached the expected length' ); + equal( rawVal, opt.expected, 'New value matches the expected string' ); + } else { - expect(1); - equal( $.byteLength( newVal ), opt.expected, 'Unlimited scenarios are not affected, expected length reached' ); + expect(2); + equal( newVal, opt.expected, 'New value matches the expected string' ); + equal( $.byteLength( newVal ), $.byteLength( opt.expected ), 'Unlimited scenarios are not affected, expected length reached' ); } - - opt.$input.remove(); } ); }; @@ -79,77 +82,106 @@ var byteLimitTest({ description: 'Plain text input', $input: $( '<input>' ) - .attr( { - 'type': 'text' - }), + .attr( 'type', 'text' ), sample: simpleSample, - useLimit: false, - expected: $.byteLength( simpleSample ) + hasLimit: false, + expected: simpleSample }); byteLimitTest({ description: 'Limit using the maxlength attribute', $input: $( '<input>' ) - .attr( { - 'type': 'text', - 'maxlength': '10' - }) + .attr( 'type', 'text' ) + .prop( 'maxLength', '10' ) .byteLimit(), sample: simpleSample, - useLimit: true, + hasLimit: true, limit: 10, - expected: 10 + expected: '1234567890' }); byteLimitTest({ description: 'Limit using a custom value', $input: $( '<input>' ) - .attr( { - 'type': 'text' - }) + .attr( 'type', 'text' ) .byteLimit( 10 ), sample: simpleSample, - useLimit: true, + hasLimit: true, limit: 10, - expected: 10 + expected: '1234567890' }); byteLimitTest({ description: 'Limit using a custom value, overriding maxlength attribute', $input: $( '<input>' ) - .attr( { - 'type': 'text', - 'maxLength': '10' - }) + .attr( 'type', 'text' ) + .prop( 'maxLength', '10' ) .byteLimit( 15 ), sample: simpleSample, - useLimit: true, + hasLimit: true, limit: 15, - expected: 15 + expected: '123456789012345' }); byteLimitTest({ description: 'Limit using a custom value (multibyte)', $input: $( '<input>' ) - .attr( { - 'type': 'text' - }) + .attr( 'type', 'text' ) .byteLimit( 14 ), sample: mbSample, - useLimit: true, + hasLimit: true, limit: 14, - expected: 14 // (10 x 1-byte char) + (1 x 3-byte char) + (1 x 1-byte char) + expected: '1234567890' + U_20AC + '1' }); byteLimitTest({ description: 'Limit using a custom value (multibyte) overlapping a byte', $input: $( '<input>' ) - .attr( { - 'type': 'text' - }) + .attr( 'type', 'text' ) .byteLimit( 12 ), sample: mbSample, - useLimit: true, + hasLimit: true, limit: 12, - expected: 12 // 10 x 1-byte char. The next 3-byte char exceeds limit of 12, but 2 more 1-byte chars come in after. + expected: '1234567890' + '12' }); + +byteLimitTest({ + description: 'Pass the limit and a callback as input filter', + $input: $( '<input>' ) + .attr( 'type', 'text' ) + .byteLimit( 6, function( val ) { + // Invalid title + if ( val == '' ) { + return ''; + } + + // Return without namespace prefix + return new mw.Title( '' + val ).getMain(); + } ), + sample: 'User:Sample', + hasLimit: true, + limit: 6, // 'Sample' length + expected: 'User:Sample' +}); + +byteLimitTest({ + description: 'Limit using the maxlength attribute and pass a callback as input filter', + $input: $( '<input>' ) + .attr( 'type', 'text' ) + .prop( 'maxLength', '6' ) + .byteLimit( function( val ) { + // Invalid title + if ( val === '' ) { + return ''; + } + + // Return without namespace prefix + return new mw.Title( '' + val ).getMain(); + } ), + sample: 'User:Sample', + hasLimit: true, + limit: 6, // 'Sample' length + expected: 'User:Sample' +}); + +}() );
\ No newline at end of file diff --git a/tests/qunit/suites/resources/jquery/jquery.client.js b/tests/qunit/suites/resources/jquery/jquery.client.test.js index 50df2928..7be41971 100644 --- a/tests/qunit/suites/resources/jquery/jquery.client.js +++ b/tests/qunit/suites/resources/jquery/jquery.client.test.js @@ -1,12 +1,14 @@ -module( 'jquery.client.js' ); +module( 'jquery.client', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect(1); ok( jQuery.client, 'jQuery.client defined' ); }); -test( 'profile userAgent support', function() { - expect(8); +/** Number of user-agent defined */ +var uacount = 0; + +var uas = (function() { // Object keyed by userAgent. Value is an array (human-readable name, client-profile object, navigator.platform value) // Info based on results from http://toolserver.org/~krinkle/testswarm/job/174/ @@ -24,11 +26,32 @@ test( 'profile userAgent support', function() { "version": "7.0", "versionBase": "7", "versionNumber": 7 + }, + wikiEditor: { + ltr: true, + rtl: false } }, // Internet Explorer 8 // Internet Explorer 9 // Internet Explorer 10 + 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)': { + title: 'Internet Explorer 10', + platform: 'Win32', + profile: { + "name": "msie", + "layout": "trident", + "layoutVersion": "unknown", // should be able to report 6? + "platform": "win", + "version": "10.0", + "versionBase": "10", + "versionNumber": 10 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, // Firefox 2 // Firefox 3.5 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.19) Gecko/20110420 Firefox/3.5.19': { @@ -42,6 +65,10 @@ test( 'profile userAgent support', function() { "version": "3.5.19", "versionBase": "3", "versionNumber": 3.5 + }, + wikiEditor: { + ltr: true, + rtl: true } }, // Firefox 3.6 @@ -56,6 +83,10 @@ test( 'profile userAgent support', function() { "version": "3.6.17", "versionBase": "3", "versionNumber": 3.6 + }, + wikiEditor: { + ltr: true, + rtl: true } }, // Firefox 4 @@ -70,7 +101,29 @@ test( 'profile userAgent support', function() { "version": "4.0.1", "versionBase": "4", "versionNumber": 4 - } + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + // Firefox 10 nightly build + 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0a1) Gecko/20111103 Firefox/10.0a1': { + title: 'Firefox 10 nightly', + platform: 'Linux', + profile: { + "name": "firefox", + "layout": "gecko", + "layoutVersion": 20111103, + "platform": "linux", + "version": "10.0a1", + "versionBase": "10", + "versionNumber": 10 + }, + wikiEditor: { + ltr: true, + rtl: true + } }, // Firefox 5 // Safari 3 @@ -86,7 +139,11 @@ test( 'profile userAgent support', function() { "version": "4.0.5", "versionBase": "4", "versionNumber": 4 - } + }, + wikiEditor: { + ltr: true, + rtl: true + } }, 'Mozilla/5.0 (Windows; U; Windows NT 6.0; cs-CZ) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7': { title: 'Safari 4', @@ -99,6 +156,10 @@ test( 'profile userAgent support', function() { "version": "4.0.5", "versionBase": "4", "versionNumber": 4 + }, + wikiEditor: { + ltr: true, + rtl: true } }, // Safari 5 @@ -122,6 +183,10 @@ test( 'profile userAgent support', function() { "version": "12.0.742.112", "versionBase": "12", "versionNumber": 12 + }, + wikiEditor: { + ltr: true, + rtl: true } }, 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30': { @@ -135,9 +200,19 @@ test( 'profile userAgent support', function() { "version": "12.0.742.68", "versionBase": "12", "versionNumber": 12 + }, + wikiEditor: { + ltr: true, + rtl: true } } }; + $.each( uas, function() { uacount++ }); + return uas; +})(); + +test( 'profile userAgent support', function() { + expect(uacount); // Generate a client profile object and compare recursively var uaTest = function( rawUserAgent, data ) { @@ -168,34 +243,37 @@ test( 'profile return validation for current user agent', function() { equal( typeof p.versionNumber, 'number', 'p.versionNumber is a number' ); }); +// Example from WikiEditor +// Make sure to use raw numbers, a string like "7.0" would fail on a +// version 10 browser since in string comparaison "10" is before "7.0" :) +var testMap = { + 'ltr': { + 'msie': [['>=', 7.0]], + 'firefox': [['>=', 2]], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 3]], + 'chrome': [['>=', 3]], + 'netscape': [['>=', 9]], + 'blackberry': false, + 'ipod': false, + 'iphone': false + }, + 'rtl': { + 'msie': [['>=', 8]], + 'firefox': [['>=', 2]], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 3]], + 'chrome': [['>=', 3]], + 'netscape': [['>=', 9]], + 'blackberry': false, + 'ipod': false, + 'iphone': false + } +}; + test( 'test', function() { expect(1); - // Example from WikiEditor - var testMap = { - 'ltr': { - 'msie': [['>=', 7]], - 'firefox': [['>=', 2]], - 'opera': [['>=', 9.6]], - 'safari': [['>=', 3]], - 'chrome': [['>=', 3]], - 'netscape': [['>=', 9]], - 'blackberry': false, - 'ipod': false, - 'iphone': false - }, - 'rtl': { - 'msie': [['>=', 8]], - 'firefox': [['>=', 2]], - 'opera': [['>=', 9.6]], - 'safari': [['>=', 3]], - 'chrome': [['>=', 3]], - 'netscape': [['>=', 9]], - 'blackberry': false, - 'ipod': false, - 'iphone': false - } - }; // .test() uses eval, make sure no exceptions are thrown // then do a basic return value type check var testMatch = $.client.test( testMap ); @@ -203,3 +281,29 @@ test( 'test', function() { equal( typeof testMatch, 'boolean', 'test returns a boolean value' ); }); + +test( 'User-agent matches against WikiEditor\'s compatibility map', function() { + expect( uacount * 2 ); // double since we test both LTR and RTL + + var $body = $( 'body' ), + bodyClasses = $body.attr( 'class' ); + + // Loop through and run tests + $.each( uas, function ( agent, data ) { + $.each( ['ltr', 'rtl'], function ( i, dir ) { + $body.removeClass( 'ltr rtl' ).addClass( dir ); + var profile = $.client.profile( { + userAgent: agent, + platform: data.platform + } ); + var testMatch = $.client.test( testMap, profile ); + $body.removeClass( dir ); + + equal( testMatch, data.wikiEditor[dir], 'testing comparison based on ' + dir + ', ' + agent ); + }); + }); + + // Restore body classes + $body.attr( 'class', bodyClasses ); +}); + diff --git a/tests/qunit/suites/resources/jquery/jquery.colorUtil.js b/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js index 93f12b82..655ee564 100644 --- a/tests/qunit/suites/resources/jquery/jquery.colorUtil.js +++ b/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js @@ -1,4 +1,4 @@ -module( 'jquery.colorUtil.js' ); +module( 'jquery.colorUtil', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect(1); diff --git a/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js b/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js index 8688f12e..6489a1f1 100644 --- a/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js @@ -1,5 +1,5 @@ test('jquery.delayedBind with data option', function() { - var $fixture = $('<div>').appendTo('body'), + var $fixture = $('<div>').appendTo('#qunit-fixture'), data = { magic: "beeswax" }, delay = 50; @@ -20,7 +20,7 @@ test('jquery.delayedBind with data option', function() { }); test('jquery.delayedBind without data option', function() { - var $fixture = $('<div>').appendTo('body'), + var $fixture = $('<div>').appendTo('#qunit-fixture'), data = { magic: "beeswax" }, delay = 50; diff --git a/tests/qunit/suites/resources/jquery/jquery.getAttrs.js b/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js index 3d3d01e1..9377a2f6 100644 --- a/tests/qunit/suites/resources/jquery/jquery.getAttrs.js +++ b/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js @@ -1,4 +1,4 @@ -module( 'jquery.getAttrs.js' ); +module( 'jquery.getAttrs', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect(1); diff --git a/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js b/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js new file mode 100644 index 00000000..4750d2b8 --- /dev/null +++ b/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js @@ -0,0 +1,239 @@ +module( 'jquery.highlightText', QUnit.newMwEnvironment() ); + +test( '-- Initial check', function() { + expect(1); + ok( $.fn.highlightText, 'jQuery.fn.highlightText defined' ); +} ); + +test( 'Check', function() { + var cases = [ + { + desc: 'Test 001', + text: 'Blue Öyster Cult', + highlight: 'Blue', + expected: '<span class="highlight">Blue</span> Öyster Cult' + }, + { + desc: 'Test 002', + text: 'Blue Öyster Cult', + highlight: 'Blue ', + expected: '<span class="highlight">Blue</span> Öyster Cult' + }, + { + desc: 'Test 003', + text: 'Blue Öyster Cult', + highlight: 'Blue Ö', + expected: '<span class="highlight">Blue</span> <span class="highlight">Ö</span>yster Cult' + }, + { + desc: 'Test 004', + text: 'Blue Öyster Cult', + highlight: 'Blue Öy', + expected: '<span class="highlight">Blue</span> <span class="highlight">Öy</span>ster Cult' + }, + { + desc: 'Test 005', + text: 'Blue Öyster Cult', + highlight: ' Blue', + expected: '<span class="highlight">Blue</span> Öyster Cult' + }, + { + desc: 'Test 006', + text: 'Blue Öyster Cult', + highlight: ' Blue ', + expected: '<span class="highlight">Blue</span> Öyster Cult' + }, + { + desc: 'Test 007', + text: 'Blue Öyster Cult', + highlight: ' Blue Ö', + expected: '<span class="highlight">Blue</span> <span class="highlight">Ö</span>yster Cult' + }, + { + desc: 'Test 008', + text: 'Blue Öyster Cult', + highlight: ' Blue Öy', + expected: '<span class="highlight">Blue</span> <span class="highlight">Öy</span>ster Cult' + }, + { + desc: 'Test 009: Highlighter broken on starting Umlaut?', + text: 'Österreich', + highlight: 'Österreich', + expected: '<span class="highlight">Österreich</span>' + }, + { + desc: 'Test 010: Highlighter broken on starting Umlaut?', + text: 'Österreich', + highlight: 'Ö', + expected: '<span class="highlight">Ö</span>sterreich' + }, + { + desc: 'Test 011: Highlighter broken on starting Umlaut?', + text: 'Österreich', + highlight: 'Öst', + expected: '<span class="highlight">Öst</span>erreich' + }, + { + desc: 'Test 012: Highlighter broken on starting Umlaut?', + text: 'Österreich', + highlight: 'Oe', + expected: 'Österreich' + }, + { + desc: 'Test 013: Highlighter broken on punctuation mark?', + text: 'So good. To be there', + highlight: 'good', + expected: 'So <span class="highlight">good</span>. To be there' + }, + { + desc: 'Test 014: Highlighter broken on space?', + text: 'So good. To be there', + highlight: 'be', + expected: 'So good. To <span class="highlight">be</span> there' + }, + { + desc: 'Test 015: Highlighter broken on space?', + text: 'So good. To be there', + highlight: ' be', + expected: 'So good. To <span class="highlight">be</span> there' + }, + { + desc: 'Test 016: Highlighter broken on space?', + text: 'So good. To be there', + highlight: 'be ', + expected: 'So good. To <span class="highlight">be</span> there' + }, + { + desc: 'Test 017: Highlighter broken on space?', + text: 'So good. To be there', + highlight: ' be ', + expected: 'So good. To <span class="highlight">be</span> there' + }, + { + desc: 'Test 018: en de Highlighter broken on special character at the end?', + text: 'So good. xbß', + highlight: 'xbß', + expected: 'So good. <span class="highlight">xbß</span>' + }, + { + desc: 'Test 019: en de Highlighter broken on special character at the end?', + text: 'So good. xbß.', + highlight: 'xbß.', + expected: 'So good. <span class="highlight">xbß.</span>' + }, + { + desc: 'Test 020: RTL he Hebrew', + text: 'חסיד אומות העולם', + highlight: 'חסיד אומות העולם', + expected: '<span class="highlight">חסיד</span> <span class="highlight">אומות</span> <span class="highlight">העולם</span>' + }, + { + desc: 'Test 021: RTL he Hebrew', + text: 'חסיד אומות העולם', + highlight: 'חסי', + expected: '<span class="highlight">חסי</span>ד אומות העולם' + }, + { + desc: 'Test 022: ja Japanese', + text: '諸国民の中の正義の人', + highlight: '諸国民の中の正義の人', + expected: '<span class="highlight">諸国民の中の正義の人</span>' + }, + { + desc: 'Test 023: ja Japanese', + text: '諸国民の中の正義の人', + highlight: '諸国', + expected: '<span class="highlight">諸国</span>民の中の正義の人' + }, + { + desc: 'Test 024: fr French text and « french quotes » (guillemets)', + text: "« L'oiseau est sur l’île »", + highlight: "« L'oiseau est sur l’île »", + expected: '<span class="highlight">«</span> <span class="highlight">L\'oiseau</span> <span class="highlight">est</span> <span class="highlight">sur</span> <span class="highlight">l’île</span> <span class="highlight">»</span>' + }, + { + desc: 'Test 025: fr French text and « french quotes » (guillemets)', + text: "« L'oiseau est sur l’île »", + highlight: "« L'oise", + expected: '<span class="highlight">«</span> <span class="highlight">L\'oise</span>au est sur l’île »' + }, + { + desc: 'Test 025a: fr French text and « french quotes » (guillemets) - does it match the single strings "«" and "L" separately?', + text: "« L'oiseau est sur l’île »", + highlight: "« L", + expected: '<span class="highlight">«</span> <span class="highlight">L</span>\'oiseau est sur <span class="highlight">l</span>’île »' + }, + { + desc: 'Test 026: ru Russian', + text: 'Праведники мира', + highlight: 'Праведники мира', + expected: '<span class="highlight">Праведники</span> <span class="highlight">мира</span>' + }, + { + desc: 'Test 027: ru Russian', + text: 'Праведники мира', + highlight: 'Праве', + expected: '<span class="highlight">Праве</span>дники мира' + }, + { + desc: 'Test 028 ka Georgian', + text: 'მთავარი გვერდი', + highlight: 'მთავარი გვერდი', + expected: '<span class="highlight">მთავარი</span> <span class="highlight">გვერდი</span>' + }, + { + desc: 'Test 029 ka Georgian', + text: 'მთავარი გვერდი', + highlight: 'მთა', + expected: '<span class="highlight">მთა</span>ვარი გვერდი' + }, + { + desc: 'Test 030 hy Armenian', + text: 'Նոնա Գափրինդաշվիլի', + highlight: 'Նոնա Գափրինդաշվիլի', + expected: '<span class="highlight">Նոնա</span> <span class="highlight">Գափրինդաշվիլի</span>' + }, + { + desc: 'Test 031 hy Armenian', + text: 'Նոնա Գափրինդաշվիլի', + highlight: 'Նոն', + expected: '<span class="highlight">Նոն</span>ա Գափրինդաշվիլի' + }, + { + desc: 'Test 032: th Thai', + text: 'พอล แอร์ดิช', + highlight: 'พอล แอร์ดิช', + expected: '<span class="highlight">พอล</span> <span class="highlight">แอร์ดิช</span>' + }, + { + desc: 'Test 033: th Thai', + text: 'พอล แอร์ดิช', + highlight: 'พอ', + expected: '<span class="highlight">พอ</span>ล แอร์ดิช' + }, + { + desc: 'Test 034: RTL ar Arabic', + text: 'بول إيردوس', + highlight: 'بول إيردوس', + expected: '<span class="highlight">بول</span> <span class="highlight">إيردوس</span>' + }, + { + desc: 'Test 035: RTL ar Arabic', + text: 'بول إيردوس', + highlight: 'بو', + expected: '<span class="highlight">بو</span>ل إيردوس' + } + ]; + expect(cases.length); + var $fixture; + + $.each(cases, function( i, item ) { + $fixture = $( '<p></p>' ).text( item.text ); + $fixture.highlightText( item.highlight ); + equals( + $fixture.html(), + $('<p>' + item.expected + '</p>').html(), // re-parse to normalize! + item.desc || undefined + ); + } ); +} ); diff --git a/tests/qunit/suites/resources/jquery/jquery.localize.js b/tests/qunit/suites/resources/jquery/jquery.localize.test.js index 40b58687..cd828634 100644 --- a/tests/qunit/suites/resources/jquery/jquery.localize.js +++ b/tests/qunit/suites/resources/jquery/jquery.localize.test.js @@ -1,4 +1,4 @@ -module( 'jquery.localize.js' ); +module( 'jquery.localize', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect(1); diff --git a/tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js index bb6d2a1b..3a2d0d83 100644 --- a/tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js +++ b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js @@ -1,10 +1,10 @@ -module( 'jquery.mwPrototypes.js' ); +module( 'jquery.mwExtension', QUnit.newMwEnvironment() ); test( 'String functions', function() { equal( $.trimLeft( ' foo bar ' ), 'foo bar ', 'trimLeft' ); equal( $.trimRight( ' foo bar ' ), ' foo bar', 'trimRight' ); - equal( $.ucFirst( 'foo'), 'Foo', 'ucFirst' ); + equal( $.ucFirst( 'foo' ), 'Foo', 'ucFirst' ); equal( $.escapeRE( '<!-- ([{+mW+}]) $^|?>' ), '<!\\-\\- \\(\\[\\{\\+mW\\+\\}\\]\\) \\$\\^\\|\\?>', 'escapeRE - Escape specials' ); @@ -36,6 +36,8 @@ test( 'Is functions', function() { strictEqual( $.isEmpty( 'string' ), false, 'isEmptry: "string"' ); strictEqual( $.isEmpty( '0' ), true, 'isEmptry: "0"' ); + strictEqual( $.isEmpty( '' ), true, 'isEmptry: ""' ); + strictEqual( $.isEmpty( 1 ), false, 'isEmptry: 1' ); strictEqual( $.isEmpty( [] ), true, 'isEmptry: []' ); strictEqual( $.isEmpty( {} ), true, 'isEmptry: {}' ); diff --git a/tests/qunit/suites/resources/jquery/jquery.tabIndex.js b/tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js index 1ff81e58..98ff5508 100644 --- a/tests/qunit/suites/resources/jquery/jquery.tabIndex.js +++ b/tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js @@ -1,4 +1,4 @@ -module( 'jquery.tabIndex.js' ); +module( 'jquery.tabIndex', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect(2); @@ -18,14 +18,11 @@ test( 'firstTabIndex', function() { '<textarea tabindex="5">Foobar</textarea>' + '</form>'; - var $testA = $( '<div>' ).html( testEnvironment ).appendTo( 'body' ); + var $testA = $( '<div>' ).html( testEnvironment ).appendTo( '#qunit-fixture' ); strictEqual( $testA.firstTabIndex(), 2, 'First tabindex should be 2 within this context.' ); var $testB = $( '<div>' ); strictEqual( $testB.firstTabIndex(), null, 'Return null if none available.' ); - - // Clean up - $testA.add( $testB ).remove(); }); test( 'lastTabIndex', function() { @@ -39,12 +36,9 @@ test( 'lastTabIndex', function() { '<textarea tabindex="5">Foobar</textarea>' + '</form>'; - var $testA = $( '<div>' ).html( testEnvironment ).appendTo( 'body' ); + var $testA = $( '<div>' ).html( testEnvironment ).appendTo( '#qunit-fixture' ); strictEqual( $testA.lastTabIndex(), 9, 'Last tabindex should be 9 within this context.' ); var $testB = $( '<div>' ); strictEqual( $testB.lastTabIndex(), null, 'Return null if none available.' ); - - // Clean up - $testA.add( $testB ).remove(); }); diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js index f47b7f40..7ecdc4b1 100644 --- a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js @@ -1,11 +1,13 @@ -(function() { +( function () { -module( 'jquery.tablesorter.test.js' ); +var config = { + wgMonthNames: ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + wgMonthNamesShort: ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + wgDefaultDateFormat: 'dmy', + wgContentLanguage: 'en' +}; -// setup hack -mw.config.set('wgMonthNames', window.wgMonthNames = ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']); -mw.config.set('wgMonthNamesShort', window.wgMonthNamesShort = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']); -mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'dmy'); +module( 'jquery.tablesorter', QUnit.newMwEnvironment( config ) ); test( '-- Initial check', function() { expect(1); @@ -21,23 +23,24 @@ test( '-- Initial check', function() { * @return jQuery */ var tableCreate = function( header, data ) { - var $table = $('<table class="sortable"><thead></thead><tbody></tbody></table>'), - $thead = $table.find('thead'), - $tbody = $table.find('tbody'); - var $tr = $('<tr>'); - $.each(header, function(i, str) { - var $th = $('<th>'); - $th.text(str).appendTo($tr); + var $table = $( '<table class="sortable"><thead></thead><tbody></tbody></table>' ), + $thead = $table.find( 'thead' ), + $tbody = $table.find( 'tbody' ), + $tr = $( '<tr>' ); + + $.each( header, function( i, str ) { + var $th = $( '<th>' ); + $th.text( str ).appendTo( $tr ); }); - $tr.appendTo($thead); + $tr.appendTo( $thead ); for (var i = 0; i < data.length; i++) { - $tr = $('<tr>'); - $.each(data[i], function(j, str) { - var $td = $('<td>'); - $td.text(str).appendTo($tr); + $tr = $( '<tr>' ); + $.each( data[i], function( j, str ) { + var $td = $( '<td>' ); + $td.text( str ).appendTo( $tr ); }); - $tr.appendTo($tbody); + $tr.appendTo( $tbody ); } return $table; }; @@ -50,12 +53,13 @@ var tableCreate = function( header, data ) { */ var tableExtract = function( $table ) { var data = []; - $table.find('tbody').find('tr').each(function(i, tr) { + + $table.find( 'tbody' ).find( 'tr' ).each( function( i, tr ) { var row = []; - $(tr).find('td,th').each(function(i, td) { - row.push($(td).text()); + $( tr ).find( 'td,th' ).each( function( i, td ) { + row.push( $( td ).text() ); }); - data.push(row); + data.push( row ); }); return data; }; @@ -75,7 +79,6 @@ var tableTest = function( msg, header, data, expected, callback ) { expect(1); var $table = tableCreate( header, data ); - //$('body').append($table); // Give caller a chance to set up sorting and manipulate the table. callback( $table ); @@ -93,18 +96,18 @@ var reversed = function(arr) { return arr2; }; -// Sample data set: some planets! -var header = ['Planet', 'Radius (km)'], - mercury = ['Mercury', '2439.7'], - venus = ['Venus', '6051.8'], - earth = ['Earth', '6371.0'], - mars = ['Mars', '3390.0'], - jupiter = ['Jupiter', '69911'], - saturn = ['Saturn', '58232']; +// Sample data set using planets named and their radius +var header = [ 'Planet' , 'Radius (km)'], + mercury = [ 'Mercury', '2439.7' ], + venus = [ 'Venus' , '6051.8' ], + earth = [ 'Earth' , '6371.0' ], + mars = [ 'Mars' , '3390.0' ], + jupiter = [ 'Jupiter', '69911' ], + saturn = [ 'Saturn' , '58232' ]; // Initial data set -var planets = [mercury, venus, earth, mars, jupiter, saturn]; -var ascendingName = [earth, jupiter, mars, mercury, saturn, venus]; +var planets = [mercury, venus, earth, mars, jupiter, saturn]; +var ascendingName = [earth, jupiter, mars, mercury, saturn, venus]; var ascendingRadius = [mercury, mars, venus, earth, saturn, jupiter]; tableTest( @@ -114,7 +117,7 @@ tableTest( ascendingName, function( $table ) { $table.tablesorter(); - $table.find('.headerSort:eq(0)').click(); + $table.find( '.headerSort:eq(0)' ).click(); } ); tableTest( @@ -124,7 +127,7 @@ tableTest( ascendingName, function( $table ) { $table.tablesorter(); - $table.find('.headerSort:eq(0)').click(); + $table.find( '.headerSort:eq(0)' ).click(); } ); tableTest( @@ -134,7 +137,7 @@ tableTest( reversed(ascendingName), function( $table ) { $table.tablesorter(); - $table.find('.headerSort:eq(0)').click().click(); + $table.find( '.headerSort:eq(0)' ).click().click(); } ); tableTest( @@ -144,7 +147,7 @@ tableTest( ascendingRadius, function( $table ) { $table.tablesorter(); - $table.find('.headerSort:eq(1)').click(); + $table.find( '.headerSort:eq(1)' ).click(); } ); tableTest( @@ -154,25 +157,23 @@ tableTest( reversed(ascendingRadius), function( $table ) { $table.tablesorter(); - $table.find('.headerSort:eq(1)').click().click(); + $table.find( '.headerSort:eq(1)' ).click().click(); } ); // Regression tests! tableTest( - 'Bug 28775: German-style short numeric dates', + 'Bug 28775: German-style (dmy) short numeric dates', ['Date'], - [ - // German-style dates are day-month-year + [ // German-style dates are day-month-year ['11.11.2011'], ['01.11.2011'], ['02.10.2011'], ['03.08.2011'], ['09.11.2011'] ], - [ - // Sorted by ascending date + [ // Sorted by ascending date ['03.08.2011'], ['02.10.2011'], ['01.11.2011'], @@ -180,25 +181,25 @@ tableTest( ['11.11.2011'] ], function( $table ) { - // @fixme reset it at end or change module to allow us to override it - mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'dmy'); + mw.config.set( 'wgDefaultDateFormat', 'dmy' ); + mw.config.set( 'wgContentLanguage', 'de' ); + $table.tablesorter(); - $table.find('.headerSort:eq(0)').click(); + $table.find( '.headerSort:eq(0)' ).click(); } ); + tableTest( - 'Bug 28775: American-style short numeric dates', + 'Bug 28775: American-style (mdy) short numeric dates', ['Date'], - [ - // American-style dates are month-day-year + [ // American-style dates are month-day-year ['11.11.2011'], ['01.11.2011'], ['02.10.2011'], ['03.08.2011'], ['09.11.2011'] ], - [ - // Sorted by ascending date + [ // Sorted by ascending date ['01.11.2011'], ['02.10.2011'], ['03.08.2011'], @@ -206,10 +207,10 @@ tableTest( ['11.11.2011'] ], function( $table ) { - // @fixme reset it at end or change module to allow us to override it - mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'mdy'); + mw.config.set( 'wgDefaultDateFormat', 'mdy' ); + $table.tablesorter(); - $table.find('.headerSort:eq(0)').click(); + $table.find( '.headerSort:eq(0)' ).click(); } ); @@ -235,6 +236,7 @@ var ipv4Sorted = [ ['204.204.132.158'], ['247.240.82.209'] ]; + tableTest( 'Bug 17141: IPv4 address sorting', ['IP'], @@ -242,7 +244,7 @@ tableTest( ipv4Sorted, function( $table ) { $table.tablesorter(); - $table.find('.headerSort:eq(0)').click(); + $table.find( '.headerSort:eq(0)' ).click(); } ); tableTest( @@ -252,7 +254,7 @@ tableTest( reversed(ipv4Sorted), function( $table ) { $table.tablesorter(); - $table.find('.headerSort:eq(0)').click().click(); + $table.find( '.headerSort:eq(0)' ).click().click(); } ); @@ -286,27 +288,36 @@ tableTest( umlautWords, umlautWordsSorted, function( $table ) { - mw.config.set('tableSorterCollation', {'ä':'ae', 'ö' : 'oe', 'ß': 'ss', 'ü':'ue'}); + mw.config.set( 'tableSorterCollation', { + 'ä': 'ae', + 'ö': 'oe', + 'ß': 'ss', + 'ü':'ue' + } ); + $table.tablesorter(); - $table.find('.headerSort:eq(0)').click(); - mw.config.set('tableSorterCollation', {}); + $table.find( '.headerSort:eq(0)' ).click(); } ); -var planetsRowspan =[["Earth","6051.8"], jupiter, ["Mars","6051.8"], mercury, saturn, venus]; -var planetsRowspanII =[jupiter, mercury, saturn, ['Venus', '6371.0'], venus, ['Venus', '3390.0']]; +var planetsRowspan = [["Earth","6051.8"], jupiter, ["Mars","6051.8"], mercury, saturn, venus]; +var planetsRowspanII = [jupiter, mercury, saturn, ['Venus', '6371.0'], venus, ['Venus', '3390.0']]; tableTest( - 'Basic planet table: Same value for multiple rows via rowspan', + 'Basic planet table: same value for multiple rows via rowspan', header, planets, planetsRowspan, function( $table ) { - //Quick&Dirty mod - $table.find('tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)').remove(); - $table.find('tr:eq(2) td:eq(1)').attr('rowspan', '3'); + // Modify the table to have a multiuple-row-spanning cell: + // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row. + $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove(); + // - Set rowspan for 2nd cell of 3rd row to 3. + // This covers the removed cell in the 4th and 5th row. + $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' ); + $table.tablesorter(); - $table.find('.headerSort:eq(0)').click(); + $table.find( '.headerSort:eq(0)' ).click(); } ); tableTest( @@ -315,11 +326,15 @@ tableTest( planets, planetsRowspanII, function( $table ) { - //Quick&Dirty mod - $table.find('tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)').remove(); - $table.find('tr:eq(2) td:eq(0)').attr('rowspan', '3'); + // Modify the table to have a multiuple-row-spanning cell: + // - Remove 1st cell of 4th row, and, 1st cell or 5th row. + $table.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove(); + // - Set rowspan for 1st cell of 3rd row to 3. + // This covers the removed cell in the 4th and 5th row. + $table.find( 'tr:eq(2) td:eq(0)' ).prop( 'rowspan', '3' ); + $table.tablesorter(); - $table.find('.headerSort:eq(0)').click(); + $table.find( '.headerSort:eq(0)' ).click(); } ); @@ -346,9 +361,10 @@ tableTest( complexMDYDates, complexMDYSorted, function( $table ) { - mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'mdy'); + mw.config.set( 'wgDefaultDateFormat', 'mdy' ); + $table.tablesorter(); - $table.find('.headerSort:eq(0)').click(); + $table.find( '.headerSort:eq(0)' ).click(); } ); @@ -362,9 +378,9 @@ tableTest( planets, ascendingNameLegacy, function( $table ) { - $table.find('tr:last').addClass('sortbottom'); + $table.find( 'tr:last' ).addClass( 'sortbottom' ); $table.tablesorter(); - $table.find('.headerSort:eq(0)').click(); + $table.find( '.headerSort:eq(0)' ).click(); } ); @@ -472,4 +488,65 @@ test( 'data-sort-value attribute, when available, should override sorting positi }); +var numbers = [ + [ '12' ], + [ '7' ], + [ '13,000'], + [ '9' ], + [ '14' ], + [ '8.0' ] +]; +var numbersAsc = [ + [ '7' ], + [ '8.0' ], + [ '9' ], + [ '12' ], + [ '14' ], + [ '13,000'] +]; + +tableTest( 'bug 8115: sort numbers with commas (ascending)', + ['Numbers'], numbers, numbersAsc, + function( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } +); + +tableTest( 'bug 8115: sort numbers with commas (descending)', + ['Numbers'], numbers, reversed(numbersAsc), + function( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click().click(); + } +); +// TODO add numbers sorting tests for bug 8115 with a different language + +test( 'bug 32888 - Tables inside a tableheader cell', function() { + expect(2); + + var $table; + $table = $( + '<table class="sortable" id="32888">' + + '<tr><th>header<table id="32888-2">'+ + '<tr><th>1</th><th>2</th></tr>' + + '</table></th></tr>' + + '<tr><td>A</td></tr>' + + '<tr><td>B</td></tr>' + + '</table>' + ); + $table.tablesorter(); + + equals( + $table.find('> thead:eq(0) > tr > th.headerSort').length, + 1, + 'Child tables inside a headercell should not interfere with sortable headers (bug 32888)' + ); + equals( + $('#32888-2').find('th.headerSort').length, + 0, + 'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)' + ); +}); + })(); diff --git a/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js b/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js new file mode 100644 index 00000000..1b2f3024 --- /dev/null +++ b/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js @@ -0,0 +1,279 @@ +module( 'jquery.textSelection', QUnit.newMwEnvironment() ); + +test( '-- Initial check', function() { + expect(1); + ok( $.fn.textSelection, 'jQuery.fn.textSelection defined' ); +} ); + +/** + * Test factory for $.fn.textSelection( 'encapsulateText' ) + * + * @param options {object} associative array containing: + * description {string} + * input {string} + * output {string} + * start {int} starting char for selection + * end {int} ending char for selection + * params {object} add'l parameters for $().textSelection( 'encapsulateText' ) + */ +var encapsulateTest = function( options ) { + var opt = $.extend({ + description: '', + before: {}, + after: {}, + replace: {} + }, options); + + opt.before = $.extend({ + text: '', + start: 0, + end: 0 + }, opt.before); + opt.after = $.extend({ + text: '', + selected: null + }, opt.after); + + test( opt.description, function() { + var tests = 1; + if ( opt.after.selected !== null ) { + tests++; + } + expect( tests ); + + var $textarea = $( '<textarea>' ); + + $( '#qunit-fixture' ).append( $textarea ); + + //$textarea.textSelection( 'setContents', opt.before.text); // this method is actually missing atm... + $textarea.val( opt.before.text ); // won't work with the WikiEditor iframe? + + var start = opt.before.start, + end = opt.before.end; + if ( window.opera ) { + // Compensate for Opera's craziness converting "\n" to "\r\n" and counting that as two chars + var newLinesBefore = opt.before.text.substring( 0, start ).split( "\n" ).length - 1, + newLinesInside = opt.before.text.substring( start, end ).split( "\n" ).length - 1; + start += newLinesBefore; + end += newLinesBefore + newLinesInside; + } + + var options = $.extend( {}, opt.replace ); // Clone opt.replace + options.selectionStart = start; + options.selectionEnd = end; + $textarea.textSelection( 'encapsulateSelection', options ); + + var text = $textarea.textSelection( 'getContents' ).replace( /\r\n/g, "\n" ); + + equal( text, opt.after.text, 'Checking full text after encapsulation' ); + + if (opt.after.selected !== null) { + var selected = $textarea.textSelection( 'getSelection' ); + equal( selected, opt.after.selected, 'Checking selected text after encapsulation.' ); + } + + } ); +}; + +var sig = { + 'pre': "--~~~~" +}, bold = { + pre: "'''", + peri: 'Bold text', + post: "'''" +}, h2 = { + 'pre': '== ', + 'peri': 'Heading 2', + 'post': ' ==', + 'regex': /^(\s*)(={1,6})(.*?)\2(\s*)$/, + 'regexReplace': "\$1==\$3==\$4", + 'ownline': true +}, ulist = { + 'pre': "* ", + 'peri': 'Bulleted list item', + 'post': "", + 'ownline': true, + 'splitlines': true +}; + +encapsulateTest({ + description: "Adding sig to end of text", + before: { + text: "Wikilove dude! ", + start: 15, + end: 15 + }, + after: { + text: "Wikilove dude! --~~~~", + selected: "" + }, + replace: sig +}); + +encapsulateTest({ + description: "Adding bold to empty", + before: { + text: "", + start: 0, + end: 0 + }, + after: { + text: "'''Bold text'''", + selected: "Bold text" // selected because it's the default + }, + replace: bold +}); + +encapsulateTest({ + description: "Adding bold to existing text", + before: { + text: "Now is the time for all good men to come to the aid of their country", + start: 20, + end: 32 + }, + after: { + text: "Now is the time for '''all good men''' to come to the aid of their country", + selected: "" // empty because it's not the default' + }, + replace: bold +}); + +encapsulateTest({ + description: "ownline option: adding new h2", + before: { + text:"Before\nAfter", + start: 7, + end: 7 + }, + after: { + text: "Before\n== Heading 2 ==\nAfter", + selected: "Heading 2" + }, + replace: h2 +}); + +encapsulateTest({ + description: "ownline option: turn a whole line into new h2", + before: { + text:"Before\nMy heading\nAfter", + start: 7, + end: 17 + }, + after: { + text: "Before\n== My heading ==\nAfter", + selected: "" + }, + replace: h2 +}); + + +encapsulateTest({ + description: "ownline option: turn a partial line into new h2", + before: { + text:"BeforeMy headingAfter", + start: 6, + end: 16 + }, + after: { + text: "Before\n== My heading ==\nAfter", + selected: "" + }, + replace: h2 +}); + + +encapsulateTest({ + description: "splitlines option: no selection, insert new list item", + before: { + text: "Before\nAfter", + start: 7, + end: 7 + }, + after: { + text: "Before\n* Bulleted list item\nAfter" + }, + replace: ulist +}); + +encapsulateTest({ + description: "splitlines option: single partial line selection, insert new list item", + before: { + text: "BeforeMy List ItemAfter", + start: 6, + end: 18 + }, + after: { + text: "Before\n* My List Item\nAfter" + }, + replace: ulist +}); + +encapsulateTest({ + description: "splitlines option: multiple lines", + before: { + text: "Before\nFirst\nSecond\nThird\nAfter", + start: 7, + end: 25 + }, + after: { + text: "Before\n* First\n* Second\n* Third\nAfter" + }, + replace: ulist +}); + + +var caretTest = function(options) { + test(options.description, function() { + expect(2); + + var $textarea = $( '<textarea>' ).text(options.text); + + $( '#qunit-fixture' ).append( $textarea ); + + if (options.mode == 'set') { + $textarea.textSelection('setSelection', { + start: options.start, + end: options.end + }); + } + + var among = function(actual, expected, message) { + if ($.isArray(expected)) { + ok($.inArray(actual, expected) !== -1 , message + ' (got ' + actual + '; expected one of ' + expected.join(', ') + ')'); + } else { + equal(actual, expected, message); + } + }; + + var pos = $textarea.textSelection('getCaretPosition', {startAndEnd: true}); + among(pos[0], options.start, 'Caret start should be where we set it.'); + among(pos[1], options.end, 'Caret end should be where we set it.'); + }); +} + +var caretSample = "Some big text that we like to work with. Nothing fancy... you know what I mean?"; + +caretTest({ + description: 'getCaretPosition with original/empty selection - bug 31847 with IE 6/7/8', + text: caretSample, + start: [0, caretSample.length], // Opera and Firefox (prior to FF 6.0) default caret to the end of the box (caretSample.length) + end: [0, caretSample.length], // Other browsers default it to the beginning (0), so check both. + mode: 'get' +}); + +caretTest({ + description: 'set/getCaretPosition with forced empty selection', + text: caretSample, + start: 7, + end: 7, + mode: 'set' +}); + +caretTest({ + description: 'set/getCaretPosition with small selection', + text: caretSample, + start: 6, + end: 11, + mode: 'set' +}); + diff --git a/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.js b/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js index bcc9b96b..d73fe5a6 100644 --- a/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.js +++ b/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js @@ -1,13 +1,9 @@ -module( 'mediawiki.special.recentchanges.js' ); +module( 'mediawiki.special.recentchanges', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect( 2 ); - ok( mw.special.recentchanges.init, - 'mw.special.recentchanges.init defined' - ); - ok( mw.special.recentchanges.updateCheckboxes, - 'mw.special.recentchanges.updateCheckboxes defined' - ); + ok( mw.special.recentchanges.init, 'mw.special.recentchanges.init defined' ); + ok( mw.special.recentchanges.updateCheckboxes, 'mw.special.recentchanges.updateCheckboxes defined' ); // TODO: verify checkboxes == [ 'nsassociated', 'nsinvert' ] }); @@ -37,34 +33,34 @@ test( '"all" namespace disable checkboxes', function() { // TODO abstract the double strictEquals // At first checkboxes are enabled - strictEqual( $( '#nsinvert' ).attr( 'disabled' ), undefined ); - strictEqual( $( '#nsassociated' ).attr( 'disabled' ), undefined ); + strictEqual( $( '#nsinvert' ).prop( 'disabled' ), false ); + strictEqual( $( '#nsassociated' ).prop( 'disabled' ), false ); // Initiate the recentchanges module mw.special.recentchanges.init(); // By default - strictEqual( $( '#nsinvert' ).attr( 'disabled' ), 'disabled' ); - strictEqual( $( '#nsassociated' ).attr( 'disabled' ), 'disabled' ); + strictEqual( $( '#nsinvert' ).prop( 'disabled' ), true ); + strictEqual( $( '#nsassociated' ).prop( 'disabled' ), true ); // select second option... var $options = $( '#namespace' ).find( 'option' ); - $options.eq(0).removeAttr( 'selected' ); - $options.eq(1).attr( 'selected', 'selected' ); + $options.eq(0).removeProp( 'selected' ); + $options.eq(1).prop( 'selected', true ); $( '#namespace' ).change(); // ... and checkboxes should be enabled again - strictEqual( $( '#nsinvert' ).attr( 'disabled' ), undefined ); - strictEqual( $( '#nsassociated' ).attr( 'disabled' ), undefined ); + strictEqual( $( '#nsinvert' ).prop( 'disabled' ), false ); + strictEqual( $( '#nsassociated' ).prop( 'disabled' ), false ); // select first option ( 'all' namespace)... - $options.eq(1).removeAttr( 'selected' ); - $options.eq(0).attr( 'selected', 'selected' );; + $options.eq(1).removeProp( 'selected' ); + $options.eq(0).prop( 'selected', true ); $( '#namespace' ).change(); - + // ... and checkboxes should now be disabled - strictEqual( $( '#nsinvert' ).attr( 'disabled' ), 'disabled' ); - strictEqual( $( '#nsassociated' ).attr( 'disabled' ), 'disabled' ); + strictEqual( $( '#nsinvert' ).prop( 'disabled' ), true ); + strictEqual( $( '#nsassociated' ).prop( 'disabled' ), true ); // DOM cleanup $env.remove(); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js new file mode 100644 index 00000000..e04111f1 --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js @@ -0,0 +1,201 @@ +( function () { + +// mw.Title relies on these three config vars +// Restore them after each test run +var config = { + "wgFormattedNamespaces": { + "-2": "Media", + "-1": "Special", + "0": "", + "1": "Talk", + "2": "User", + "3": "User talk", + "4": "Wikipedia", + "5": "Wikipedia talk", + "6": "File", + "7": "File talk", + "8": "MediaWiki", + "9": "MediaWiki talk", + "10": "Template", + "11": "Template talk", + "12": "Help", + "13": "Help talk", + "14": "Category", + "15": "Category talk", + // testing custom / localized namespace + "100": "Penguins" + }, + "wgNamespaceIds": { + "media": -2, + "special": -1, + "": 0, + "talk": 1, + "user": 2, + "user_talk": 3, + "wikipedia": 4, + "wikipedia_talk": 5, + "file": 6, + "file_talk": 7, + "mediawiki": 8, + "mediawiki_talk": 9, + "template": 10, + "template_talk": 11, + "help": 12, + "help_talk": 13, + "category": 14, + "category_talk": 15, + "image": 6, + "image_talk": 7, + "project": 4, + "project_talk": 5, + /* testing custom / alias */ + "penguins": 100, + "antarctic_waterfowl": 100 + }, + "wgCaseSensitiveNamespaces": [] +}; + +module( 'mediawiki.Title', QUnit.newMwEnvironment( config ) ); + +test( '-- Initial check', function () { + expect(1); + ok( mw.Title, 'mw.Title defined' ); +}); + +test( 'Transformation', function () { + expect(8); + + var title; + + title = new mw.Title( 'File:quux pif.jpg' ); + equal( title.getName(), 'Quux_pif' ); + + title = new mw.Title( 'File:Glarg_foo_glang.jpg' ); + equal( title.getNameText(), 'Glarg foo glang' ); + + title = new mw.Title( 'User:ABC.DEF' ); + equal( title.toText(), 'User:ABC.DEF' ); + equal( title.getNamespaceId(), 2 ); + equal( title.getNamespacePrefix(), 'User:' ); + + title = new mw.Title( 'uSEr:hAshAr' ); + equal( title.toText(), 'User:HAshAr' ); + equal( title.getNamespaceId(), 2 ); + + title = new mw.Title( ' MediaWiki: Foo bar .js ' ); + // Don't ask why, it's the way the backend works. One space is kept of each set + equal( title.getName(), 'Foo_bar_.js', "Merge multiple spaces to a single space." ); +}); + +test( 'Main text for filename', function () { + expect(8); + + var title = new mw.Title( 'File:foo_bar.JPG' ); + + equal( title.getNamespaceId(), 6 ); + equal( title.getNamespacePrefix(), 'File:' ); + equal( title.getName(), 'Foo_bar' ); + equal( title.getNameText(), 'Foo bar' ); + equal( title.getMain(), 'Foo_bar.JPG' ); + equal( title.getMainText(), 'Foo bar.JPG' ); + equal( title.getExtension(), 'JPG' ); + equal( title.getDotExtension(), '.JPG' ); +}); + +test( 'Namespace detection and conversion', function () { + expect(6); + + var title; + + title = new mw.Title( 'something.PDF', 6 ); + equal( title.toString(), 'File:Something.PDF' ); + + title = new mw.Title( 'NeilK', 3 ); + equal( title.toString(), 'User_talk:NeilK' ); + equal( title.toText(), 'User talk:NeilK' ); + + title = new mw.Title( 'Frobisher', 100 ); + equal( title.toString(), 'Penguins:Frobisher' ); + + title = new mw.Title( 'antarctic_waterfowl:flightless_yet_cute.jpg' ); + equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' ); + + title = new mw.Title( 'Penguins:flightless_yet_cute.jpg' ); + equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' ); +}); + +test( 'Throw error on invalid title', function () { + expect(1); + + raises(function () { + var title = new mw.Title( '' ); + }, 'Throw error on empty string' ); +}); + +test( 'Case-sensivity', function () { + expect(3); + + var title; + + // Default config + mw.config.set( 'wgCaseSensitiveNamespaces', [] ); + + title = new mw.Title( 'article' ); + equal( title.toString(), 'Article', 'Default config: No sensitive namespaces by default. First-letter becomes uppercase' ); + + // $wgCapitalLinks = false; + mw.config.set( 'wgCaseSensitiveNamespaces', [0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15] ); + + title = new mw.Title( 'article' ); + equal( title.toString(), 'article', '$wgCapitalLinks=false: Article namespace is sensitive, first-letter case stays lowercase' ); + + title = new mw.Title( 'john', 2 ); + equal( title.toString(), 'User:John', '$wgCapitalLinks=false: User namespace is insensitive, first-letter becomes uppercase' ); +}); + +test( 'toString / toText', function () { + expect(2); + + var title = new mw.Title( 'Some random page' ); + + equal( title.toString(), title.getPrefixedDb() ); + equal( title.toText(), title.getPrefixedText() ); +}); + +test( 'Exists', function () { + expect(3); + + var title; + + // Empty registry, checks default to null + + title = new mw.Title( 'Some random page', 4 ); + strictEqual( title.exists(), null, 'Return null with empty existance registry' ); + + // Basic registry, checks default to boolean + mw.Title.exist.set( ['Does_exist', 'User_talk:NeilK', 'Wikipedia:Sandbox_rules'], true ); + mw.Title.exist.set( ['Does_not_exist', 'User:John', 'Foobar'], false ); + + title = new mw.Title( 'Project:Sandbox rules' ); + assertTrue( title.exists(), 'Return true for page titles marked as existing' ); + title = new mw.Title( 'Foobar' ); + assertFalse( title.exists(), 'Return false for page titles marked as nonexistent' ); + +}); + +test( 'Url', function () { + expect(2); + + var title; + + // Config + mw.config.set( 'wgArticlePath', '/wiki/$1' ); + + title = new mw.Title( 'Foobar' ); + equal( title.getUrl(), '/wiki/Foobar', 'Basic functionally, toString passing to wikiGetlink' ); + + title = new mw.Title( 'John Doe', 3 ); + equal( title.getUrl(), '/wiki/User_talk:John_Doe', 'Escaping in title and namespace for urls' ); +}); + +}() );
\ No newline at end of file diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js new file mode 100644 index 00000000..265ec2ae --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -0,0 +1,43 @@ +module( 'mediawiki.jqueryMsg' ); + +test( '-- Initial check', function() { + expect( 1 ); + ok( mw.jqueryMsg, 'mw.jqueryMsg defined' ); +} ); + +test( 'mw.jqueryMsg Plural', function() { + expect( 5 ); + var parser = mw.jqueryMsg.getMessageFunction(); + ok( parser, 'Parser Function initialized' ); + ok( mw.messages.set( 'plural-msg', 'Found $1 {{PLURAL:$1|item|items}}' ), 'mw.messages.set: Register' ); + equal( parser( 'plural-msg', 0 ) , 'Found 0 items', 'Plural test for english with zero as count' ); + equal( parser( 'plural-msg', 1 ) , 'Found 1 item', 'Singular test for english' ); + equal( parser( 'plural-msg', 2 ) , 'Found 2 items', 'Plural test for english' ); +} ); + + +test( 'mw.jqueryMsg Gender', function() { + expect( 16 ); + //TODO: These tests should be for mw.msg once mw.msg integrated with mw.jqueryMsg + var user = mw.user; + user.options.set( 'gender', 'male' ); + var parser = mw.jqueryMsg.getMessageFunction(); + ok( parser, 'Parser Function initialized' ); + //TODO: English may not be the best language for these tests. Use a language like Arabic or Russian + ok( mw.messages.set( 'gender-msg', '$1 reverted {{GENDER:$2|his|her|their}} last edit' ), 'mw.messages.set: Register' ); + equal( parser( 'gender-msg', 'Bob', 'male' ) , 'Bob reverted his last edit', 'Gender masculine' ); + equal( parser( 'gender-msg', 'Bob', user ) , 'Bob reverted his last edit', 'Gender masculine' ); + user.options.set( 'gender', 'unknown' ); + equal( parser( 'gender-msg', 'They', user ) , 'They reverted their last edit', 'Gender neutral or unknown' ); + equal( parser( 'gender-msg', 'Alice', 'female' ) , 'Alice reverted her last edit', 'Gender feminine' ); + equal( parser( 'gender-msg', 'User' ) , 'User reverted their last edit', 'Gender neutral' ); + equal( parser( 'gender-msg', 'User', 'unknown' ) , 'User reverted their last edit', 'Gender neutral' ); + ok( mw.messages.set( 'gender-msg-one-form', '{{GENDER:$1|User}} reverted last $2 {{PLURAL:$2|edit|edits}}' ), 'mw.messages.set: Register' ); + equal( parser( 'gender-msg-one-form', 'male', 10 ) , 'User reverted last 10 edits', 'Gender neutral and plural form' ); + equal( parser( 'gender-msg-one-form', 'female', 1 ) , 'User reverted last 1 edit', 'Gender neutral and singular form' ); + ok( mw.messages.set( 'gender-msg-lowercase', '{{gender:$1|he|she}} is awesome' ), 'mw.messages.set: Register' ); + equal( parser( 'gender-msg-lowercase', 'male' ) , 'he is awesome', 'Gender masculine' ); + equal( parser( 'gender-msg-lowercase', 'female' ) , 'she is awesome', 'Gender feminine' ); + ok( mw.messages.set( 'gender-msg-wrong', '{{gender}} is awesome' ), 'mw.messages.set: Register' ); + equal( parser( 'gender-msg-wrong', 'female' ) , ' is awesome', 'Wrong syntax used, but ignore the {{gender}}' ); +} ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js index 52cd32c8..24005b64 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js @@ -1,6 +1,6 @@ /* Some misc JavaScript compatibility tests, just to make sure the environments we run in are consistent */ -module( 'mediawiki.jscompat' ); +module( 'mediawiki.jscompat', QUnit.newMwEnvironment() ); test( 'Variable with Unicode letter in name', function() { expect(3); @@ -33,3 +33,30 @@ test( 'Keyword workaround: "if" as member variable name using Unicode escapes', deepEqual( foo.\u0069\u0066, orig, 'foo.\\u0069\\u0066' ); }); */ + +test( 'Stripping of single initial newline from textarea\'s literal contents (bug 12130)', function() { + var maxn = 4; + expect(maxn * 2); + + var repeat = function(str, n) { + if (n <= 0) { + return ''; + } else { + var out = Array(n); + for (var i = 0; i < n; i++) { + out[i] = str; + } + return out.join(''); + } + }; + + for (var n = 0; n < maxn; n++) { + var expected = repeat('\n', n) + 'some text'; + + var $textarea = $('<textarea>\n' + expected + '</textarea>'); + equal($textarea.val(), expected, 'Expecting ' + n + ' newlines (HTML contained ' + (n + 1) + ')'); + + var $textarea2 = $('<textarea>').val(expected); + equal($textarea2.val(), expected, 'Expecting ' + n + ' newlines (from DOM set with ' + n + ')'); + } +}); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.js b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js index 4beed881..e6934eda 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js @@ -1,4 +1,4 @@ -module( 'mediawiki.js' ); +module( 'mediawiki', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect(8); @@ -80,7 +80,7 @@ test( 'mw.config', function() { }); test( 'mw.message & mw.messages', function() { - expect(17); + expect(20); ok( mw.messages, 'messages defined' ); ok( mw.messages instanceof mw.Map, 'mw.messages instance of mw.Map' ); @@ -88,7 +88,7 @@ test( 'mw.message & mw.messages', function() { var hello = mw.message( 'hello' ); - equal( hello.format, 'parse', 'Message property "format" defaults to "parse"' ); + equal( hello.format, 'plain', 'Message property "format" defaults to "plain"' ); strictEqual( hello.map, mw.messages, 'Message property "map" defaults to the global instance in mw.messages' ); equal( hello.key, 'hello', 'Message property "key" (currect key)' ); deepEqual( hello.parameters, [], 'Message property "parameters" defaults to an empty array' ); @@ -111,59 +111,45 @@ test( 'mw.message & mw.messages', function() { strictEqual( hello.exists(), true, 'Message.exists returns true for existing messages' ); var goodbye = mw.message( 'goodbye' ); - strictEqual( goodbye.exists(), false, 'Message.exists returns false for inexisting messages' ); + strictEqual( goodbye.exists(), false, 'Message.exists returns false for nonexistent messages' ); equal( goodbye.plain(), '<goodbye>', 'Message.toString returns plain <key> if format is "plain" and key does not exist' ); // bug 30684 equal( goodbye.escaped(), '<goodbye>', 'Message.toString returns properly escaped <key> if format is "escaped" and key does not exist' ); + + ok( mw.messages.set( 'pluraltestmsg', 'There {{PLURAL:$1|is|are}} $1 {{PLURAL:$1|result|results}}' ), 'mw.messages.set: Register' ); + var pluralMessage = mw.message( 'pluraltestmsg' , 6 ); + equal( pluralMessage.plain(), 'There are 6 results', 'plural get resolved when format is plain' ); + equal( pluralMessage.parse(), 'There are 6 results', 'plural get resolved when format is parse' ); + }); test( 'mw.msg', function() { - expect(3); + expect(11); ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' ); - equal( mw.msg( 'hello' ), 'Hello <b>awesome</b> world', 'Gets message with default options (existing message)' ); - equal( mw.msg( 'goodbye' ), '<goodbye>', 'Gets message with default options (inexisting message)' ); -}); + equal( mw.msg( 'goodbye' ), '<goodbye>', 'Gets message with default options (nonexistent message)' ); -test( 'mw.loader', function() { - expect(5); + ok( mw.messages.set( 'plural-item' , 'Found $1 {{PLURAL:$1|item|items}}' ) ); + equal( mw.msg( 'plural-item', 5 ), 'Found 5 items', 'Apply plural for count 5' ); + equal( mw.msg( 'plural-item', 0 ), 'Found 0 items', 'Apply plural for count 0' ); + equal( mw.msg( 'plural-item', 1 ), 'Found 1 item', 'Apply plural for count 1' ); - // Regular expression to extract the path for the QUnit tests - // Takes into account that tests could be run from a file:// URL - // by excluding the 'index.html' part from the URL - var rePath = /(?:[^#\?](?!index.html))*\/?/; + ok( mw.messages.set('gender-plural-msg' , '{{GENDER:$1|he|she|they}} {{PLURAL:$2|is|are}} awesome' ) ); + equal( mw.msg( 'gender-plural-msg', 'male', 1 ), 'he is awesome', 'Gender test for male, plural count 1' ); + equal( mw.msg( 'gender-plural-msg', 'female', '1' ), 'she is awesome', 'Gender test for female, plural count 1' ); + equal( mw.msg( 'gender-plural-msg', 'unknown', 10 ), 'they are awesome', 'Gender test for neutral, plural count 10' ); - // Four assertions to test the above regular expression: - equal( - rePath.exec( 'http://path/to/tests/?foobar' )[0], - "http://path/to/tests/", - "Extracting path from http URL with query" - ); - equal( - rePath.exec( 'http://path/to/tests/#frag' )[0], - "http://path/to/tests/", - "Extracting path from http URL with fragment" - ); - equal( - rePath.exec( 'file://path/to/tests/index.html?foobar' )[0], - "file://path/to/tests/", - "Extracting path from local URL (file://) with query" - ); - equal( - rePath.exec( 'file://path/to/tests/index.html#frag' )[0], - "file://path/to/tests/", - "Extracting path from local URL (file://) with fragment" - ); +}); - // Asynchronous ahead - stop(5000); +test( 'mw.loader', function() { + expect(1); - // Extract path - var tests_path = rePath.exec( location.href ); + // Asynchronous ahead + stop(); - mw.loader.implement( 'is.awesome', [QUnit.fixurl( tests_path + 'data/defineTestCallback.js')], {}, {} ); + mw.loader.implement( 'is.awesome', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/defineTestCallback.js' )], {}, {} ); mw.loader.using( 'is.awesome', function() { @@ -185,9 +171,9 @@ test( 'mw.loader.bug29107' , function() { // Message doesn't exist already ok( !mw.messages.exists( 'bug29107' ) ); - // Async! Include a timeout, as failure in this test leads to neither the - // success nor failure callbacks getting called. - stop(5000); + // Async! Failure in this test may lead to neither the success nor error callbacks getting called. + // Due to QUnit's timeout feauture we won't hang here forever if this happends. + stop(); mw.loader.implement( 'bug29107.messages-only', [], {}, {'bug29107': 'loaded'} ); mw.loader.using( 'bug29107.messages-only', function() { @@ -199,8 +185,31 @@ test( 'mw.loader.bug29107' , function() { }); }); +test( 'mw.loader.bug30825', function() { + // This bug was actually already fixed in 1.18 and later when discovered in 1.17. + // Test is for regressions! + + expect(2); + + // Forge an URL to the test callback script + var target = QUnit.fixurl( + mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js' + ); + + // Confirm that mw.loader.load() works with protocol-relative URLs + target = target.replace( /https?:/, '' ); + + equal( target.substr( 0, 2 ), '//', + 'URL must be relative to test relative URLs!' + ); + + // Async! + stop(); + mw.loader.load( target ); +}); + test( 'mw.html', function() { - expect(7); + expect(11); raises( function(){ mw.html.escape(); @@ -214,11 +223,40 @@ test( 'mw.html', function() { equal( mw.html.element( 'div' ), '<div/>', 'html.element DIV (simple)' ); - equal( mw.html.element( 'div', - { id: 'foobar' } ), + equal( + mw.html.element( + 'div', { + id: 'foobar' + } + ), '<div id="foobar"/>', 'html.element DIV (attribs)' ); + equal( mw.html.element( 'p', null, 12 ), '<p>12</p>', 'Numbers are valid content and should be casted to a string' ); + + equal( mw.html.element( 'p', { title: 12 }, '' ), '<p title="12"></p>', 'Numbers are valid attribute values' ); + + equal( + mw.html.element( + 'option', { + selected: true + }, 'Foo' + ), + '<option selected="selected">Foo</option>', + 'Attributes may have boolean values. True copies the attribute name to the value.' + ); + + equal( + mw.html.element( + 'option', { + value: 'foo', + selected: false + }, 'Foo' + ), + '<option value="foo">Foo</option>', + 'Attributes may have boolean values. False keeps the attribute from output.' + ); + equal( mw.html.element( 'div', null, 'a' ), '<div>a</div>', diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.user.js b/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js index d5c6baad..15265db5 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.user.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js @@ -1,4 +1,4 @@ -module( 'mediawiki.user.js' ); +module( 'mediawiki.user', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect(1); @@ -16,6 +16,16 @@ test( 'options', function() { test( 'User login status', function() { expect(5); + /** + * Tests can be run under three different conditions: + * 1) From tests/qunit/index.html, user will be anonymous. + * 2) Logged in on [[Special:JavaScriptTest/qunit]] + * 3) Anonymously at the same special page. + */ + + // Forge an anonymous user: + mw.config.set( 'wgUserName', null); + strictEqual( mw.user.name(), null, 'user.name should return null when anonymous' ); ok( mw.user.anonymous(), 'user.anonymous should reutrn true when anonymous' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.util.js b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js index 9c05d9b2..ea28935e 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.util.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js @@ -1,4 +1,4 @@ -module( 'mediawiki.util.js' ); +module( 'mediawiki.util', QUnit.newMwEnvironment() ); test( '-- Initial check', function() { expect(1); @@ -47,13 +47,12 @@ test( 'wikiScript', function() { equal( mw.util.wikiScript(), mw.config.get( 'wgScript' ), 'Defaults to index.php and is equal to wgScript' ); equal( mw.util.wikiScript( 'api' ), '/w/api.php', 'API path' ); - }); test( 'addCSS', function() { expect(3); - var $testEl = $( '<div>' ).attr( 'id', 'mw-addcsstest' ).appendTo( 'body' ); + var $testEl = $( '<div>' ).attr( 'id', 'mw-addcsstest' ).appendTo( '#qunit-fixture' ); var style = mw.util.addCSS( '#mw-addcsstest { visibility: hidden; }' ); equal( typeof style, 'object', 'addCSS returned an object' ); @@ -62,9 +61,7 @@ test( 'addCSS', function() { equal( $testEl.css( 'visibility' ), 'hidden', 'Added style properties are in effect' ); // Clean up - $( style.ownerNode ) - .add( $testEl ) - .remove(); + $( style.ownerNode ).remove(); }); test( 'toggleToc', function() { @@ -80,7 +77,7 @@ test( 'toggleToc', function() { '</div>' + '<ul><li></li></ul>' + '</td></tr></table>', - $toc = $(tocHtml).appendTo( 'body' ), + $toc = $(tocHtml).appendTo( '#qunit-fixture' ), $toggleLink = $( '#togglelink' ); strictEqual( $toggleLink.length, 1, 'Toggle link is appended to the page.' ); @@ -91,9 +88,6 @@ test( 'toggleToc', function() { var actionC = function() { start(); - - // Clean up - $toc.remove(); }; var actionB = function() { start(); stop(); @@ -109,18 +103,18 @@ test( 'toggleToc', function() { test( 'getParamValue', function() { expect(5); - var url1 = 'http://mediawiki.org/?foo=wrong&foo=right#&foo=bad'; + var url1 = 'http://example.org/?foo=wrong&foo=right#&foo=bad'; equal( mw.util.getParamValue( 'foo', url1 ), 'right', 'Use latest one, ignore hash' ); strictEqual( mw.util.getParamValue( 'bar', url1 ), null, 'Return null when not found' ); - var url2 = 'http://mediawiki.org/#&foo=bad'; + var url2 = 'http://example.org/#&foo=bad'; strictEqual( mw.util.getParamValue( 'foo', url2 ), null, 'Ignore hash if param is not in querystring but in hash (bug 27427)' ); - var url3 = 'example.com?' + $.param({ 'TEST': 'a b+c' }); + var url3 = 'example.org?' + $.param({ 'TEST': 'a b+c' }); strictEqual( mw.util.getParamValue( 'TEST', url3 ), 'a b+c', 'Bug 30441: getParamValue must understand "+" encoding of space' ); - var url4 = 'example.com?' + $.param({ 'TEST': 'a b+c d' }); // check for sloppy code from r95332 :) + var url4 = 'example.org?' + $.param({ 'TEST': 'a b+c d' }); // check for sloppy code from r95332 :) strictEqual( mw.util.getParamValue( 'TEST', url4 ), 'a b+c d', 'Bug 30441: getParamValue must understand "+" encoding of space (multiple spaces)' ); }); @@ -139,51 +133,55 @@ test( '$content', function() { strictEqual( mw.util.$content.length, 1, 'mw.util.$content must have length of 1' ); }); + +/** + * Portlet names are prefixed with 'p-test' to avoid conflict with core + * when running the test suite under a wiki page. + * Previously, test elements where invisible to the selector since only + * one element can have a given id. + */ test( 'addPortletLink', function() { expect(7); var mwPanel = '<div id="mw-panel" class="noprint">\ <h5>Toolbox</h5>\ - <div class="portlet" id="p-tb">\ + <div class="portlet" id="p-test-tb">\ <ul class="body"></ul>\ </div>\ </div>', - vectorTabs = '<div id="p-views" class="vectorTabs">\ + vectorTabs = '<div id="p-test-views" class="vectorTabs">\ <h5>Views</h5>\ <ul></ul>\ </div>', - $mwPanel = $(mwPanel).appendTo( 'body' ), - $vectorTabs = $(vectorTabs).appendTo( 'body' ); + $mwPanel = $(mwPanel).appendTo( '#qunit-fixture' ), + $vectorTabs = $(vectorTabs).appendTo( '#qunit-fixture' ); - var tbRL = mw.util.addPortletLink( 'p-tb', 'http://mediawiki.org/wiki/ResourceLoader', + var tbRL = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/ResourceLoader', 'ResourceLoader', 't-rl', 'More info about ResourceLoader on MediaWiki.org ', 'l' ); ok( $.isDomElement( tbRL ), 'addPortletLink returns a valid DOM Element according to $.isDomElement' ); - var tbMW = mw.util.addPortletLink( 'p-tb', 'http://mediawiki.org/', + var tbMW = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/', 'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org ', 'm', tbRL ), $tbMW = $( tbMW ); - + equal( $tbMW.attr( 'id' ), 't-mworg', 'Link has correct ID set' ); - equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-tb', 'Link was inserted within correct portlet' ); + equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-test-tb', 'Link was inserted within correct portlet' ); equal( $tbMW.next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing nextnode)' ); - var tbRLDM = mw.util.addPortletLink( 'p-tb', 'http://mediawiki.org/wiki/RL/DM', + var tbRLDM = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm', 'List of all default modules ', 'd', '#t-rl' ); equal( $( tbRLDM ).next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing CSS selector)' ); - var caFoo = mw.util.addPortletLink( 'p-views', '#', 'Foo' ); + var caFoo = mw.util.addPortletLink( 'p-test-views', '#', 'Foo' ); strictEqual( $tbMW.find( 'span').length, 0, 'No <span> element should be added for porlets without vectorTabs class.' ); strictEqual( $( caFoo ).find( 'span').length, 1, 'A <span> element should be added for porlets with vectorTabs class.' ); - + // Clean up - $( [tbRL, tbMW, tbRLDM, caFoo] ) - .add( $mwPanel ) - .add( $vectorTabs ) - .remove(); + $( [tbRL, tbMW, tbRLDM, caFoo] ).remove(); }); test( 'jsMessage', function() { diff --git a/tests/selenium/SeleniumConfig.php b/tests/selenium/SeleniumConfig.php index b8cdf1c5..b1487154 100644 --- a/tests/selenium/SeleniumConfig.php +++ b/tests/selenium/SeleniumConfig.php @@ -5,7 +5,7 @@ if ( !defined( 'SELENIUMTEST' ) ) { class SeleniumConfig { - /* + /** * Retreives the Selenium configuration values from an ini file. * See sample config file in selenium_settings.ini.sample * @@ -72,14 +72,14 @@ class SeleniumConfig { return false; } $header = ''; - + $configArray = array(); - + while ( ( $line = fgets( $file ) ) !== false ) { $line = strtok( $line, "\r\n" ); - + if ( !$line || $line[0] == ';' ) continue; - + if ( $line[0] == '[' && substr( $line, -1 ) == ']' ) { $header = substr( $line, 1, -1 ); $configArray[$header] = array(); @@ -95,19 +95,19 @@ class SeleniumConfig { list( $key, $value ) = explode( '=', $iniLine, 2 ); $key = trim( $key ); $value = trim( $value ); - + if ( isset( $specialValues[$value] ) ) { $value = $specialValues[$value]; } else { $value = trim( $value, '"' ); } - + /* Support one-level arrays */ if ( preg_match( '/^([A-Za-z]+)\[([A-Za-z]+)\]/', $key, $m ) ) { $key = $m[1]; $value = array( $m[2] => $value ); } - + return array( $key => $value ); } } diff --git a/tests/selenium/data/SimpleSeleniumTestDB.sql b/tests/selenium/data/SimpleSeleniumTestDB.sql index 7944c45f..1a3196c3 100644 --- a/tests/selenium/data/SimpleSeleniumTestDB.sql +++ b/tests/selenium/data/SimpleSeleniumTestDB.sql @@ -1295,7 +1295,6 @@ CREATE TABLE `mw_user` ( `user_newpassword` tinyblob NOT NULL, `user_newpass_time` binary(14) DEFAULT NULL, `user_email` tinytext NOT NULL, - `user_options` blob NOT NULL, `user_touched` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', `user_token` binary(32) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', `user_email_authenticated` binary(14) DEFAULT NULL, diff --git a/tests/selenium/data/mediawiki118_fresh_installation.sql b/tests/selenium/data/mediawiki118_fresh_installation.sql index 89bc3191..2724bad5 100644 --- a/tests/selenium/data/mediawiki118_fresh_installation.sql +++ b/tests/selenium/data/mediawiki118_fresh_installation.sql @@ -767,13 +767,13 @@ CREATE TABLE `mw_objectcache` ( LOCK TABLES `mw_objectcache` WRITE; /*!40000 ALTER TABLE `mw_objectcache` DISABLE KEYS */; -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:messages:en','K2.2R\ns\r\nSδ2\0','2010-12-31 13:16:31'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:pcache:idhash:1-0!*!*!!en!*','V[oFg~BPm)$TKU\08$9a\rIlX>ܹqSa,ʕx?[~ʃ|.Z$8Y\'IYK-\04UJ\'&uB):IEmsk``kQva\rZt0+P%GEـJX;\n-sP@Bb8~첒$eaυf+0[,Fxd\'z0BJ=Jc\\:&BT\'CFdÿ׆FqGd%8G0AI; Ԙ`75LI\r({cg+8Qr&ͦA)VЕPT\\UƧtnZeSfJZ(VP}0ON =j\\Hy\\U[h]T:bdu+j%\'6kf:E;@Yך4ȀqZº6<b3TU(d,\nY|e\'5TfU8}\"m/}Uk9o;|*R?n 3dg1y\\f8gkw=:/Y7ۋ^<Ōv#iC#6\Z.0Ua$4=\Z;4Y=5:kpΐqŦ4XCqYߵ-LjDf:(3t14CJ#WXTΔy:^6v7IUEe(p0ga6MjSc,ѫ@ޅ+RAxХ\'6utǷbۛ`j8ؚGIC<KS5|krJ\ry\\b3xPua@$SS`tQ.gwW\r@\'w xZ(>5{dw>=J)\r6t XMB\nŖTb>qg7zn7vwr-%u-QiiX1NeA#vӧ3?','2010-12-31 13:16:31'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:pcache:idoptions:1','E@D`\"vƣWh,b!⭙7L+|}tI$<F\rpSl4OJN`\r\Zծ)PY$Kգ9Vjp72EcWp2cVxu7 p#r=.[>y)Zp','2010-12-31 13:16:31'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-css:3832ee25d9c44988461f5f339b9b6a48','+26RrMMLTHɩV\0Z((3(Rd\r\0','2038-01-19 03:14:07'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-css:aa0df16258ad99a1d249e796b5067ed9','+2RrMMLTHɩNK-Q.,LNJ,R\0s\rV\Z\0','2038-01-19 03:14:07'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-js:22814eeadc9cf0a9ebcd844e14198e66','mr0yr&Qޡמ!\nqQXq;}$ވ c!]]].o5S\n)FqL^?sF!OM\\\0NɁլ:-jF{ۅG\"i \Z6K!Y]=F[~竍䶃`9NǴ@K|z1A@J#_ԁ7\'l1)J͵).3zfTAHњ[#)BzRA7\"T*~SW/PBŎ;\Zay6+U?.$6-uTv@hs&NإbfJ~]6p/q)>E1͔A\neLg\ZE`cW`fJEa>b\nӑd.udo[\ntb+l\Z?X*Y(օ;LJqťɝd$\"WzG-@b~+#kǞَƂ~P)B qҖ2rRl`z 4ÝXm;X݁t;r.sARy)kA\nRJTJU*W_ߟ4@vtf>x','2038-01-19 03:14:07'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-js:dd9440c19c575629ac5ec90e489cf62e','+21RԔLĔ\"ĒTj̒T%+ĔJZMk.%k\0','2038-01-19 03:14:07'); +INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:messages:en','K�2��.�2�R\ns\r\n���S�δ2��\0','2010-12-31 13:16:31'); +INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:pcache:idhash:1-0!*!*!!en!*','�V[o�F�g~�����B��P��m)$TK�U��\0�����8$��9�a\rI�l��X>��ܹ�q�Sa,��ʕ�x��?[~ʃ|�.Z��$�8Y�\'�I�Y�K��-\04�U����J�\'&�uB)�:I������E�m�sk`�`k�Q�v��a���\rZ��t�0����+P%GE�ـ�JX;\n-s�P�@�B�b���8~�첒$��ea�υ��f��+���0[,�F�x��d�\'�z�0��BJ���=���J��c�\\��:��&B��T��\'��C��Fdÿ׆Fq����Gd����%8G�0��A�I��; Ԙ`�7�5�LI\r��(���{�c�����g+��8Qr�&�ͦ��A)�V��ЕPT��\\UƧtn��Z�e�SfJZ(V�P�}����0��O�N �=j�\\H��y�\\�U[h]T:���bd��u��+�j%\'�6k��f:E�;�@Yך��4���Ȁ��q���Z�º6<b�3��TU(d��,\n���Y|�e�\'�5��T�fU�8}�\"��m���/���}Uk�9o��;����|*R?�n��� 3d��g1��y�\\f8gk�����w��=��:/Y7���ۋ�^<�Ō����v#���i����C�#��6\Z�.0�Ua$4�=\Z���;��4����Y=���5���:kpΐq��Ŧ4��X���C��qYߵ-��Lj�����D�f�����:����(3t��14C��J�#����������WXT���Δy:�^�6�v�7����I�U�Ee��(�p0��ga�6Mj��Sc�,ѫ@��ޅ+R����A��xХ\'6���utǷbۛ��`j�8ؚ�G�IC<KS��5�|�krJ\ry�\\b3xP�����ua�@����$SS�`��tQ.gwW��\r@\'���w�� ��xZ(�>5{����dw�>�=J)\r�6t ��X����M���B�\n�ŖT����b>�qg����7��z�n7��vwr�-%u�-Qi�iX1��Ne���A#�v��ӧ�3��?','2010-12-31 13:16:31'); +INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:pcache:idoptions:1','E���@D��`�\"v�ƣ��Wh��,�b�!�⭙7�L+�|}�t��I�$�<���F\rpSl�4����OJN`\r\Z����ծ���)��PY��$�K����գ9�Vjp72��E���c�Wp�2��cVxu7�� ����p#�r=.���[>y)Zp��','2010-12-31 13:16:31'); +INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-css:3832ee25d9c44988461f5f339b9b6a48','+�26�Rr�MM�LTH�ɩV\0�Z(��(3�(R�d\r\0','2038-01-19 03:14:07'); +INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-css:aa0df16258ad99a1d249e796b5067ed9','+�2��Rr�MM�LTH�ɩN��K-�Q.,�L�NJ,R\0��s�\r���V�\Z\0','2038-01-19 03:14:07'); +INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-js:22814eeadc9cf0a9ebcd844e14198e66','m��r�0��y����r�&Qޡמ!\n�qQ�Xq;}���$��ވ� �c!]]].o5S�\n�)Fq��L^��?�s�F�!�O�M\\�������\0���N��Ɂ���լ����:��-�j��F��{ۅ�G�\"i�� \Z�6�K����!��Y]=�F[�~竍���䶃����`��9N�Ǵ���@�K��|z�?1�A��@J#_ԁ�7\'�l�1)J�͵�).�3z�f�T�A���Hњ�[#)�BzRA�7��\"T�*~SW���/P���B�Ŏ;\Z�ay�6����+U��?.$�6��-uT�v@h��s�&�����Nإb�fJ�~�]6��p��/q)�>�E�1��͔A\ne�L�g\ZE�`cW�����`fJ�E�a��>��b\n�ӑd�.u�do��[�\nt��b�+���l\Z?X*��Y�(�օ;�L�Jqťɝ���d$�\"�WzG�-@b~+�#�kǞَ�Ƃ~������P)B ����q�Җ2���r�Rl����`z �4�����ÝX�m�;�X݁t;r.�sA��R��y)�kA�\nR�JT��J�U��*�W��_ߟ�4@�vt��f���>����x���','2038-01-19 03:14:07'); +INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-js:dd9440c19c575629ac5ec90e489cf62e','+�21�R���Ԕ�����L���Ĕ�\"��ĒT�j��̒T%+���ĔJ�ZMk.%k\0','2038-01-19 03:14:07'); /*!40000 ALTER TABLE `mw_objectcache` ENABLE KEYS */; UNLOCK TABLES; @@ -1384,7 +1384,6 @@ CREATE TABLE `mw_user` ( `user_newpassword` tinyblob NOT NULL, `user_newpass_time` binary(14) DEFAULT NULL, `user_email` tinytext NOT NULL, - `user_options` blob NOT NULL, `user_touched` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', `user_token` binary(32) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', `user_email_authenticated` binary(14) DEFAULT NULL, diff --git a/tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php b/tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php index 3557f516..bf5b379d 100644 --- a/tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php +++ b/tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php @@ -30,7 +30,7 @@ require_once (dirname(__FILE__).'/'.'MediaWikiInstallationCommonFunction.php'); -/* +/** * Test Case ID : 30 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name :'Back' and 'Continue' button availability * Version : MediaWiki 1.18alpha diff --git a/tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php b/tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php index 4afcdc0e..f1b79459 100644 --- a/tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php +++ b/tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php @@ -30,7 +30,7 @@ require_once ( dirname( __FILE__ ) . '/MediaWikiInstallationCommonFunction.php' ); -/* +/** * Test Case ID : 04 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : Install MediaWiki with different Database accounts for web access. * Version : MediaWiki 1.18alpha diff --git a/tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php b/tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php index b6a0fc09..2d623afc 100644 --- a/tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php +++ b/tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php @@ -29,7 +29,7 @@ require_once ( dirname( __FILE__ ) . '/MediaWikiInstallationCommonFunction.php' ); -/* +/** * Test Case ID : 02 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : Install MediaWiki with the same database and the different * database prefixes(Share one database between multiple wikis). diff --git a/tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php b/tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php index 3642a8ef..b112bc0e 100644 --- a/tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php +++ b/tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php @@ -30,7 +30,7 @@ require_once ( dirname( __FILE__ ) . '/MediaWikiInstallationCommonFunction.php' ); -/* +/** * Test Case ID : 09 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : Invalid/ blank values for fields in 'Connect to database' page. * Version : MediaWiki 1.18alpha diff --git a/tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php b/tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php index d70dcc42..024fe5d6 100644 --- a/tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php +++ b/tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php @@ -27,7 +27,7 @@ * */ -/* +/** * Test Case ID : 10 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : Invalid/ blank values for fields in 'Name' page. * Version : MediaWiki 1.18alpha diff --git a/tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php b/tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php index 355a2857..806fcfde 100644 --- a/tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php +++ b/tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php @@ -27,7 +27,7 @@ * */ -/* +/** * Test Case ID : 29 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : Help field hint availability for the fields. * Version : MediaWiki 1.18alpha diff --git a/tests/selenium/installer/MediaWikiInstallationConfig.php b/tests/selenium/installer/MediaWikiInstallationConfig.php index d3067d69..d86bcb85 100644 --- a/tests/selenium/installer/MediaWikiInstallationConfig.php +++ b/tests/selenium/installer/MediaWikiInstallationConfig.php @@ -28,7 +28,7 @@ */ -/* +/** * MediaWikiInstallerTestSuite.php can be run one time successfully * with current value of the 'DB_NAME_PREFIX'. * If you wish to run the suite more than one time, you need to change @@ -39,7 +39,7 @@ define('DIRECTORY_NAME', "mediawiki" ); define( 'PORT', "8080" ); define( 'HOST_NAME', "localhost" ); -/* +/** * Use the followings to run the test suite in different browsers. * Firefox : *firefox * IE : *iexplore diff --git a/tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php b/tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php index abf9ddf2..399ed4e5 100644 --- a/tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php +++ b/tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php @@ -30,7 +30,7 @@ require_once (dirname(__FILE__).'/'.'MediaWikiInstallationCommonFunction.php'); -/* +/** * Test Case ID : 01 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : Install Mediawiki using 'MySQL' database type successfully * Version : MediaWiki 1.18alpha diff --git a/tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php b/tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php index fe704a42..f57c1a55 100644 --- a/tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php +++ b/tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php @@ -30,7 +30,7 @@ require_once (dirname(__FILE__).'/'.'MediaWikiInstallationCommonFunction.php'); -/* +/** * Test Case ID : 06 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : Install Mediawiki using 'MySQL' database type successfully * Version : MediaWiki 1.18alpha diff --git a/tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php b/tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php index e8b8f9b0..4c052666 100644 --- a/tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php +++ b/tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php @@ -31,7 +31,7 @@ require_once (dirname(__FILE__).'/'.'MediaWikiInstallationCommonFunction.php'); -/* +/** * Test Case ID : 03 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : Install mediawiki on a already installed Mediawiki.] * Version : MediaWiki 1.18alpha diff --git a/tests/selenium/installer/MediaWikiRestartInstallationTestCase.php b/tests/selenium/installer/MediaWikiRestartInstallationTestCase.php index b0a38000..b9ca8305 100644 --- a/tests/selenium/installer/MediaWikiRestartInstallationTestCase.php +++ b/tests/selenium/installer/MediaWikiRestartInstallationTestCase.php @@ -31,7 +31,7 @@ require_once (dirname(__FILE__).'/'.'MediaWikiInstallationCommonFunction.php'); -/* +/** * Test Case ID : 11, 12 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : Install mediawiki on a already installed Mediawiki. * Version : MediaWiki 1.18alpha diff --git a/tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php b/tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php index 346f24f8..700172c2 100644 --- a/tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php +++ b/tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php @@ -30,7 +30,7 @@ require_once (dirname(__FILE__).'/'.'MediaWikiInstallationCommonFunction.php'); -/* +/** * Test Case ID : 14, 15, 16, 17 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : User selects 'Read me' link. * User selects 'Release notes' link. diff --git a/tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php b/tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php index 0ab5e659..eb82071e 100644 --- a/tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php +++ b/tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php @@ -30,7 +30,7 @@ require_once (dirname(__FILE__).'/'.'MediaWikiInstallationCommonFunction.php'); -/* +/** * Test Case ID : 05 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : Install Mediawiki by updating the existing database. * Version : MediaWiki 1.18alpha diff --git a/tests/selenium/installer/MediaWikiUserInterfaceTestCase.php b/tests/selenium/installer/MediaWikiUserInterfaceTestCase.php index 7be39c04..0994892f 100644 --- a/tests/selenium/installer/MediaWikiUserInterfaceTestCase.php +++ b/tests/selenium/installer/MediaWikiUserInterfaceTestCase.php @@ -29,7 +29,7 @@ require_once (dirname(__FILE__).'/'.'MediaWikiInstallationCommonFunction.php'); -/* +/** * Test Case ID : 18 - 27 (http://www.mediawiki.org/wiki/New_installer/Test_plan) * Test Case Name : UI of MediaWiki initial/ Language/ Welcome to MediaWiki!/ Connect to database/ * Database settings/ Name/ Options/ Install/ Complete/ Restart Inslation pages diff --git a/tests/selenium/suites/MediawikiCoreSmokeTestCase.php b/tests/selenium/suites/MediawikiCoreSmokeTestCase.php index 7b9525af..5fc1a5a6 100644 --- a/tests/selenium/suites/MediawikiCoreSmokeTestCase.php +++ b/tests/selenium/suites/MediawikiCoreSmokeTestCase.php @@ -1,44 +1,44 @@ <?php -/* +/* * Stub of tests be need as part of the hack-a-ton */ class MediawikiCoreSmokeTestCase extends SeleniumTestCase { public function testUserLogin() { - + } - + public function testChangeUserPreference() { - + } - - /* + + /** * TODO: generalize this test to be reusable for different skins */ public function testCreateNewPageVector() { - + } - - /* + + /** * TODO: generalize this test to be reusable for different skins */ public function testEditExistingPageVector() { - + } - - /* + + /** * TODO: generalize this test to be reusable for different skins */ public function testCreateNewPageMonobook() { - + } - - /* + + /** * TODO: generalize this test to be reusable for different skins */ public function testEditExistingPageMonobook() { - + } - + public function testImageUpload() { $this->login(); $this->open( $this->getUrl() . @@ -48,10 +48,10 @@ class MediawikiCoreSmokeTestCase extends SeleniumTestCase { $this->check( 'wpIgnoreWarning' ); $this->click( 'wpUpload' ); $this->waitForPageToLoad( 30000 ); - + $this->assertSeleniumHTMLContains( '//h1[@class="firstHeading"]', "Wikipedia-logo-v2-de.png" ); - + /* $this->open( $this->getUrl() . '/index.php?title=Image:' . ucfirst( $this->filename ) . '&action=delete' ); @@ -64,6 +64,6 @@ class MediawikiCoreSmokeTestCase extends SeleniumTestCase { ucfirst( $this->filename ) . '.*has been deleted.' ); */ } - + } diff --git a/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php b/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php index 5d5ef518..a9a9b4d6 100644 --- a/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php +++ b/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php @@ -1,5 +1,5 @@ <?php -/* +/** * Stubs for now. We're going to start populating this test. */ class MediawikiCoreSmokeTestSuite extends SeleniumTestSuite diff --git a/tests/selenium/suites/SimpleSeleniumTestCase.php b/tests/selenium/suites/SimpleSeleniumTestCase.php index 99a75c12..b87172e6 100644 --- a/tests/selenium/suites/SimpleSeleniumTestCase.php +++ b/tests/selenium/suites/SimpleSeleniumTestCase.php @@ -1,11 +1,11 @@ <?php -/* +/* * This test case is part of the SimpleSeleniumTestSuite. * Configuration for these tests are documented as part of SimpleSeleniumTestSuite.php */ class SimpleSeleniumTestCase extends SeleniumTestCase { public function testBasic() { - $this->open( $this->getUrl() . + $this->open( $this->getUrl() . '/index.php?title=Selenium&action=edit' ); $this->type( "wpTextbox1", "This is a basic test" ); $this->click( "wpPreview" ); @@ -16,8 +16,8 @@ class SimpleSeleniumTestCase extends SeleniumTestCase { $correct = strstr( $source, "This is a basic test" ); $this->assertEquals( $correct, true ); } - - /* + + /** * All this test really does is verify that a global var was set. * It depends on $wgDefaultSkin = 'chick'; being set */ @@ -26,9 +26,9 @@ class SimpleSeleniumTestCase extends SeleniumTestCase { $bodyClass = $this->getAttribute( "//body/@class" ); $this-> assertContains('skin-chick', $bodyClass, 'Chick skin not set'); } - - /* - * Just verify that the test db was loaded correctly + + /** + * Just verify that the test db was loaded correctly */ public function testDatabaseResourceLoadedCorrectly() { $this->open( $this->getUrl() . '/index.php/TestResources?action=purge' ); diff --git a/tests/selenium/suites/SimpleSeleniumTestSuite.php b/tests/selenium/suites/SimpleSeleniumTestSuite.php index 3f5e3645..2e0c4ee2 100644 --- a/tests/selenium/suites/SimpleSeleniumTestSuite.php +++ b/tests/selenium/suites/SimpleSeleniumTestSuite.php @@ -1,5 +1,5 @@ <?php -/* +/** * Sample test suite. * Two ways to configure MW for these tests * 1) If you are running multiple test suites, add the following in LocalSettings.php diff --git a/tests/testHelpers.inc b/tests/testHelpers.inc index 5d56e625..7fc60a5c 100644 --- a/tests/testHelpers.inc +++ b/tests/testHelpers.inc @@ -1,53 +1,5 @@ <?php -/** - * @ingroup Testing - * - * Set of classes to help with test output and such. Right now pretty specific - * to the parser tests but could be more useful one day :) - * - * @todo Fixme: Make this more generic - */ - -class AnsiTermColorer { - function __construct() { - } - - /** - * Return ANSI terminal escape code for changing text attribs/color - * - * @param $color String: semicolon-separated list of attribute/color codes - * @return String - */ - public function color( $color ) { - global $wgCommandLineDarkBg; - - $light = $wgCommandLineDarkBg ? "1;" : "0;"; - - return "\x1b[{$light}{$color}m"; - } - - /** - * Return ANSI terminal escape code for restoring default text attributes - * - * @return String - */ - public function reset() { - return $this->color( 0 ); - } -} - -/* A colour-less terminal */ -class DummyTermColorer { - public function color( $color ) { - return ''; - } - - public function reset() { - return ''; - } -} - class TestRecorder { var $parent; var $term; @@ -121,8 +73,8 @@ class DbTestPreviewer extends TestRecorder { function start() { parent::start(); - if ( ! $this->db->tableExists( 'testrun' ) - or ! $this->db->tableExists( 'testitem' ) ) + if ( ! $this->db->tableExists( 'testrun', __METHOD__ ) + || ! $this->db->tableExists( 'testitem', __METHOD__ ) ) { print "WARNING> `testrun` table not found in database.\n"; $this->prevRun = false; @@ -305,7 +257,7 @@ class DbTestRecorder extends DbTestPreviewer { $this->db->begin(); if ( ! $this->db->tableExists( 'testrun' ) - or ! $this->db->tableExists( 'testitem' ) ) + || ! $this->db->tableExists( 'testitem' ) ) { print "WARNING> `testrun` table not found in database. Trying to create table.\n"; $this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) ); @@ -355,12 +307,12 @@ class TestFileIterator implements Iterator { private $parserTest; /* An instance of ParserTest (parserTests.php) or MediaWikiParserTest (phpunit) */ private $index = 0; private $test; + private $section = null; /** String|null: current test section being analyzed */ + private $sectionData = array(); private $lineNum; private $eof; function __construct( $file, $parserTest ) { - global $IP; - $this->file = $file; $this->fh = fopen( $this->file, "rt" ); @@ -369,7 +321,6 @@ class TestFileIterator implements Iterator { } $this->parserTest = $parserTest; - $this->parserTest->showRunFile( wfRelativePath( $this->file, $IP ) ); $this->lineNum = $this->index = 0; } @@ -409,128 +360,223 @@ class TestFileIterator implements Iterator { } function readNextTest() { - $data = array(); - $section = null; + $this->clearSection(); + + # Create a fake parser tests which never run anything unless + # asked to do so. This will avoid running hooks for a disabled test + $delayedParserTest = new DelayedParserTest(); while ( false !== ( $line = fgets( $this->fh ) ) ) { $this->lineNum++; $matches = array(); if ( preg_match( '/^!!\s*(\w+)/', $line, $matches ) ) { - $section = strtolower( $matches[1] ); + $this->section = strtolower( $matches[1] ); - if ( $section == 'endarticle' ) { - if ( !isset( $data['text'] ) ) { - throw new MWException( "'endarticle' without 'text' at line {$this->lineNum} of $this->file\n" ); - } - - if ( !isset( $data['article'] ) ) { - throw new MWException( "'endarticle' without 'article' at line {$this->lineNum} of $this->file\n" ); - } + if ( $this->section == 'endarticle' ) { + $this->checkSection( 'text' ); + $this->checkSection( 'article' ); - $this->parserTest->addArticle( ParserTest::chomp( $data['article'] ), $data['text'], $this->lineNum ); + $this->parserTest->addArticle( ParserTest::chomp( $this->sectionData['article'] ), $this->sectionData['text'], $this->lineNum ); - $data = array(); - $section = null; + $this->clearSection(); continue; } - if ( $section == 'endhooks' ) { - if ( !isset( $data['hooks'] ) ) { - throw new MWException( "'endhooks' without 'hooks' at line {$this->lineNum} of $this->file\n" ); - } + if ( $this->section == 'endhooks' ) { + $this->checkSection( 'hooks' ); - foreach ( explode( "\n", $data['hooks'] ) as $line ) { + foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) { $line = trim( $line ); if ( $line ) { - if ( !$this->parserTest->requireHook( $line ) ) { - return false; - } + $delayedParserTest->requireHook( $line ); } } - $data = array(); - $section = null; + $this->clearSection(); continue; } - if ( $section == 'endfunctionhooks' ) { - if ( !isset( $data['functionhooks'] ) ) { - throw new MWException( "'endfunctionhooks' without 'functionhooks' at line {$this->lineNum} of $this->file\n" ); - } + if ( $this->section == 'endfunctionhooks' ) { + $this->checkSection( 'functionhooks' ); - foreach ( explode( "\n", $data['functionhooks'] ) as $line ) { + foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) { $line = trim( $line ); if ( $line ) { - if ( !$this->parserTest->requireFunctionHook( $line ) ) { - return false; - } + $delayedParserTest->requireFunctionHook( $line ); } } - $data = array(); - $section = null; + $this->clearSection(); continue; } - if ( $section == 'end' ) { - if ( !isset( $data['test'] ) ) { - throw new MWException( "'end' without 'test' at line {$this->lineNum} of $this->file\n" ); - } - - if ( !isset( $data['input'] ) ) { - throw new MWException( "'end' without 'input' at line {$this->lineNum} of $this->file\n" ); - } + if ( $this->section == 'end' ) { + $this->checkSection( 'test' ); + $this->checkSection( 'input' ); + $this->checkSection( 'result' ); - if ( !isset( $data['result'] ) ) { - throw new MWException( "'end' without 'result' at line {$this->lineNum} of $this->file\n" ); + if ( !isset( $this->sectionData['options'] ) ) { + $this->sectionData['options'] = ''; } - if ( !isset( $data['options'] ) ) { - $data['options'] = ''; + if ( !isset( $this->sectionData['config'] ) ) { + $this->sectionData['config'] = ''; } - if ( !isset( $data['config'] ) ) - $data['config'] = ''; - - if ( ( ( preg_match( '/\\bdisabled\\b/i', $data['options'] ) && !$this->parserTest->runDisabled ) - || !preg_match( "/" . $this->parserTest->regex . "/i", $data['test'] ) ) ) { + if ( ( ( preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) && !$this->parserTest->runDisabled ) + || !preg_match( "/" . $this->parserTest->regex . "/i", $this->sectionData['test'] ) ) ) { # disabled test - $data = array(); - $section = null; + $this->clearSection(); + + # Forget any pending hooks call since test is disabled + $delayedParserTest->reset(); continue; } + # We are really going to run the test, run pending hooks and hooks function + wfDebug( __METHOD__ . " unleashing delayed test for: {$this->sectionData['test']}" ); + $hooksResult = $delayedParserTest->unleash( $this->parserTest ); + if( !$hooksResult ) { + # Some hook reported an issue. Abort. + return false; + } + $this->test = array( - 'test' => ParserTest::chomp( $data['test'] ), - 'input' => ParserTest::chomp( $data['input'] ), - 'result' => ParserTest::chomp( $data['result'] ), - 'options' => ParserTest::chomp( $data['options'] ), - 'config' => ParserTest::chomp( $data['config'] ) ); + 'test' => ParserTest::chomp( $this->sectionData['test'] ), + 'input' => ParserTest::chomp( $this->sectionData['input'] ), + 'result' => ParserTest::chomp( $this->sectionData['result'] ), + 'options' => ParserTest::chomp( $this->sectionData['options'] ), + 'config' => ParserTest::chomp( $this->sectionData['config'] ), + ); return true; } - if ( isset ( $data[$section] ) ) { + if ( isset ( $this->sectionData[$this->section] ) ) { throw new MWException( "duplicate section '$section' at line {$this->lineNum} of $this->file\n" ); } - $data[$section] = ''; + $this->sectionData[$this->section] = ''; continue; } - if ( $section ) { - $data[$section] .= $line; + if ( $this->section ) { + $this->sectionData[$this->section] .= $line; } } return false; } + + + /** + * Clear section name and its data + */ + private function clearSection() { + $this->sectionData = array(); + $this->section = null; + + } + + /** + * Verify the current section data has some value for the given token + * name (first parameter). + * Throw an exception if it is not set, referencing current section + * and adding the current file name and line number + * + * @param $token String: expected token that should have been mentionned before closing this section + */ + private function checkSection( $token ) { + if( is_null( $this->section ) ) { + throw new MWException( __METHOD__ . " can not verify a null section!\n" ); + } + + if( !isset($this->sectionData[$token]) ) { + throw new MWException( sprintf( + "'%s' without '%s' at line %s of %s\n", + $this->section, + $token, + $this->lineNum, + $this->file + )); + } + return true; + } } + +/** + * A class to delay execution of a parser test hooks. + */ +class DelayedParserTest { + + /** Initialized on construction */ + private $hooks; + private $fnHooks; + + public function __construct() { + $this->reset(); + } + + /** + * Init/reset or forgot about the current delayed test. + * Call to this will erase any hooks function that were pending. + */ + public function reset() { + $this->hooks = array(); + $this->fnHooks = array(); + } + + /** + * Called whenever we actually want to run the hook. + * Should be the case if we found the parserTest is not disabled + */ + public function unleash( &$parserTest ) { + if( !($parserTest instanceof ParserTest || $parserTest instanceof NewParserTest + ) ) { + throw new MWException( __METHOD__ . " must be passed an instance of ParserTest or NewParserTest classes\n" ); + } + + # Trigger delayed hooks. Any failure will make us abort + foreach( $this->hooks as $hook ) { + $ret = $parserTest->requireHook( $hook ); + if( !$ret ) { + return false; + } + } + + # Trigger delayed function hooks. Any failure will make us abort + foreach( $this->fnHooks as $fnHook ) { + $ret = $parserTest->requireFunctionHook( $fnHook ); + if( !$ret ) { + return false; + } + } + + # Delayed execution was successful. + return true; + } + + /** + * Similar to ParserTest object but does not run anything + * Use unleash() to really execute the hook + */ + public function requireHook( $hook ) { + $this->hooks[] = $hook; + } + /** + * Similar to ParserTest object but does not run anything + * Use unleash() to really execute the hook function + */ + public function requireFunctionHook( $fnHook ) { + $this->fnHooks[] = $fnHook; + } + +}
\ No newline at end of file |