28 | At any time, try pressing ⌘+right, shift+left or ctrl+shift+alt+d. 29 |
30 | 31 |32 | When a input, a select or a textarea element is focused, key inputs should be ignored. 33 |
34 | 35 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /test/evidence.js: -------------------------------------------------------------------------------- 1 | /* evidence.js, version 0.6 2 | * 3 | * Copyright (c) 2009 Tobie Langel (http://tobielangel.com) 4 | * 5 | * evidence.js is freely distributable under the terms of an MIT-style license. 6 | *--------------------------------------------------------------------------*/ 7 | 8 | (function(global) { 9 | var originalEvidence = global.Evidence, 10 | originalOnload = global.onload; 11 | 12 | function Evidence() { 13 | TestCase.extend.apply(TestCase, arguments); 14 | } 15 | 16 | function noConflict() { 17 | global.Evidence = originalEvidence; 18 | return Evidence; 19 | } 20 | 21 | Evidence.noConflict = noConflict; 22 | Evidence.VERSION = '0.6'; 23 | 24 | var FILE_REGEXP = /.*?\/(\w+\.html)(.*)/; 25 | 26 | function getNameFromFile() { 27 | return (global.location || '').toString().replace(FILE_REGEXP, '$1'); 28 | } 29 | 30 | function chain(subclass, superclass) { 31 | function Subclass() {} 32 | Subclass.prototype = superclass.prototype; 33 | subclass.prototype = new Subclass(); 34 | subclass.prototype.constructor = subclass; 35 | return subclass; 36 | } 37 | 38 | function defer(block, context) { 39 | if ('setTimeout' in global) { 40 | window.setTimeout(function() { 41 | block.call(context); 42 | }, 10); 43 | } else { 44 | block.call(context); 45 | } 46 | } 47 | function AssertionSkippedError(message) { 48 | this.message = message; 49 | } 50 | 51 | AssertionSkippedError.displayName = 'AssertionSkippedError'; 52 | 53 | (function(p) { 54 | p.name = 'AssertionSkippedError'; 55 | })(AssertionSkippedError.prototype); 56 | Evidence.AssertionSkippedError = AssertionSkippedError; 57 | function AssertionFailedError(message, template, args) { 58 | this.message = message; 59 | this.template = template || ''; 60 | this.args = args; 61 | } 62 | 63 | AssertionFailedError.displayName = 'AssertionFailedError'; 64 | 65 | (function(p) { 66 | p.name = 'AssertionFailedError'; 67 | })(AssertionFailedError.prototype); 68 | Evidence.AssertionFailedError = AssertionFailedError; 69 | function AssertionMessage(message, template, args) { 70 | this.message = message.replace(/%/g, '%%'); 71 | this.template = template || ''; 72 | this.args = args; 73 | } 74 | 75 | AssertionMessage.displayName = 'AssertionMessage'; 76 | 77 | (function(p) { 78 | function toString() { 79 | return UI.printf(this.message + this.template, this.args); 80 | } 81 | p.toString = toString; 82 | })(AssertionMessage.prototype); 83 | Evidence.AssertionMessage = AssertionMessage; 84 | 85 | var Assertions = (function() { 86 | function _assertExpression(expression, message, template) { 87 | /*for (var i=0; i < 100000; i++) { 88 | (function(){})() 89 | }*/ 90 | if (expression) { 91 | this.addAssertion(); 92 | } else { 93 | var args = Array.prototype.slice.call(arguments, 3); 94 | throw new AssertionFailedError(message, template, args); 95 | } 96 | } 97 | 98 | function skip(message) { 99 | throw new AssertionSkippedError(message || 'Skipped!'); 100 | } 101 | 102 | function fail(message) { 103 | this._assertExpression(false, message || 'Flunked!'); 104 | } 105 | 106 | function assert(test, message) { 107 | this._assertExpression( 108 | !!test, 109 | message || 'Failed assertion.', 110 | 'Expected %o to evaluate to true.', test 111 | ); 112 | } 113 | 114 | function refute(test, message) { 115 | this._assertExpression( 116 | !test, 117 | message || 'Failed refutation.', 118 | 'Expected %o to evaluate to false.', test 119 | ); 120 | } 121 | 122 | function assertTrue(test, message) { 123 | this._assertExpression( 124 | (test === true), 125 | message || 'Failed assertion.', 126 | 'Expected %o to be true.', test 127 | ); 128 | } 129 | 130 | function refuteTrue(test, message) { 131 | this._assertExpression( 132 | (test !== true), 133 | message || 'Failed refutation.', 134 | 'Expected %o to not be true.', test 135 | ); 136 | } 137 | 138 | function assertNull(test, message) { 139 | this._assertExpression( 140 | (test === null), 141 | message || 'Failed assertion.', 142 | 'Expected %o to be null.', test 143 | ); 144 | } 145 | 146 | function refuteNull(test, message) { 147 | this._assertExpression( 148 | (test !== null), 149 | message || 'Failed refutation.', 150 | 'Expected %o to not be null.', test 151 | ); 152 | } 153 | 154 | function assertUndefined(test, message) { 155 | this._assertExpression( 156 | (typeof test === 'undefined'), 157 | message || 'Failed assertion.', 158 | 'Expected %o to be undefined.', test 159 | ); 160 | } 161 | 162 | function refuteUndefined(test, message) { 163 | this._assertExpression( 164 | (typeof test !== 'undefined'), 165 | message || 'Failed refutation.', 166 | 'Expected %o to not be undefined.', test 167 | ); 168 | } 169 | 170 | function assertFalse(test, message) { 171 | this._assertExpression( 172 | (test === false), 173 | message || 'Failed assertion.', 174 | 'Expected %o to be false.', test 175 | ); 176 | } 177 | 178 | function refuteFalse(test, message) { 179 | this._assertExpression( 180 | (test !== false), 181 | message || 'Failed refutation.', 182 | 'Expected %o to not be false.', test 183 | ); 184 | } 185 | 186 | function assertEqual(expected, actual, message) { 187 | this._assertExpression( 188 | (expected == actual), 189 | message || 'Failed assertion.', 190 | 'Expected %o to be == to %o.', actual, expected 191 | ); 192 | } 193 | 194 | function refuteEqual(expected, actual, message) { 195 | this._assertExpression( 196 | (expected != actual), 197 | message || 'Failed refutation.', 198 | 'Expected %o to be != to %o.', actual, expected 199 | ); 200 | } 201 | 202 | function assertIdentical(expected, actual, message) { 203 | this._assertExpression( 204 | (expected === actual), 205 | message || 'Failed assertion.', 206 | 'Expected %o to be === to %o.', actual, expected 207 | ); 208 | } 209 | 210 | function refuteIdentical(expected, actual, message) { 211 | this._assertExpression( 212 | (expected !== actual), 213 | message || 'Failed refutation.', 214 | 'Expected %o to be !== to %o.', actual, expected 215 | ); 216 | } 217 | 218 | function assertIn(property, object, message) { 219 | this._assertExpression( 220 | (property in object), 221 | message || 'Failed assertion.', 222 | 'Expected "%s" to be a property of %o.', property, object 223 | ); 224 | } 225 | 226 | function refuteIn(property, object, message) { 227 | this._assertExpression( 228 | !(property in object), 229 | message || 'Failed refutation.', 230 | 'Expected "%s" to not be a property of %o.', property, object 231 | ); 232 | } 233 | 234 | return { 235 | _assertExpression: _assertExpression, 236 | skip: skip, 237 | assert: assert, 238 | refute: refute, 239 | assertNot: refute, 240 | assertTrue: assertTrue, 241 | assertNull: assertNull, 242 | assertUndefined: assertUndefined, 243 | assertFalse: assertFalse, 244 | assertIdentical: assertIdentical, 245 | refuteIdentical: refuteIdentical, 246 | assertEqual: assertEqual, 247 | refuteEqual: refuteEqual, 248 | assertIn: assertIn, 249 | refuteIn: refuteIn, 250 | fail: fail, 251 | flunk: fail 252 | }; 253 | })(); 254 | Evidence.Assertions = Assertions; 255 | function TestCase(methodName) { 256 | this._methodName = methodName; 257 | this.name = methodName; 258 | } 259 | 260 | (function() { 261 | function extend(name, methods) { 262 | function TestCaseSubclass(methodName) { 263 | TestCase.call(this, methodName); 264 | } 265 | 266 | if (!methods) { 267 | methods = name; 268 | name = getNameFromFile(); 269 | } 270 | 271 | chain(TestCaseSubclass, this); 272 | TestCaseSubclass.displayName = name; 273 | TestCaseSubclass.extend = extend; 274 | 275 | for(var prop in methods) { 276 | TestCaseSubclass.prototype[prop] = methods[prop]; 277 | } 278 | TestCase.subclasses.push(TestCaseSubclass); 279 | return TestCaseSubclass; 280 | } 281 | 282 | function AssertionsMixin() {} 283 | AssertionsMixin.prototype = Assertions; 284 | TestCase.prototype = new AssertionsMixin(); 285 | TestCase.constructor = TestCase; 286 | 287 | TestCase.displayName = 'TestCase'; 288 | TestCase.extend = extend; 289 | TestCase.subclasses = []; 290 | TestCase.defaultTimeout = 10000; 291 | })(); 292 | 293 | (function(p) { 294 | function run(result) { 295 | if (result) { this._result = result; } 296 | try { 297 | if (this._nextAssertions) { 298 | this._result.restartTest(this); 299 | this._nextAssertions(this); 300 | } else { 301 | /*this._globalProperties = objectKeys(global);*/ 302 | this._result.startTest(this); 303 | this.setUp(this); 304 | this[this._methodName](this); 305 | } 306 | } catch(e) { 307 | this._filterException(e); 308 | } finally { 309 | if (this._paused) { 310 | this._result.pauseTest(this); 311 | } else { 312 | try { 313 | this.tearDown(this); 314 | } catch(e) { 315 | this._filterException(e); 316 | } finally { 317 | this._nextAssertions = null; 318 | this._result.stopTest(this); 319 | defer(function() { 320 | this.parent.next(); 321 | }, this); 322 | } 323 | } 324 | } 325 | } 326 | 327 | function _filterException(e) { 328 | var name = e.name; 329 | switch(name) { 330 | case 'AssertionFailedError': 331 | this._result.addFailure(this, e); 332 | break; 333 | case 'AssertionSkippedError': 334 | this._result.addSkip(this, e); 335 | break; 336 | default: 337 | this._result.addError(this, e); 338 | } 339 | } 340 | 341 | function pause(assertions) { 342 | this._paused = true; 343 | var self = this; 344 | if (assertions) { this._nextAssertions = assertions; } 345 | self._timeoutId = global.setTimeout(function() { 346 | self.resume(function() { 347 | self.fail('Test timed out. Testing was not resumed after being paused.'); 348 | }); 349 | }, TestCase.defaultTimeout); 350 | } 351 | 352 | function resume(assertions) { 353 | if (this._paused) { // avoid race conditions 354 | this._paused = false; 355 | global.clearTimeout(this._timeoutId); 356 | if (assertions) { this._nextAssertions = assertions; } 357 | this.run(); 358 | } 359 | } 360 | 361 | function size() { 362 | return 1; 363 | } 364 | 365 | function toString() { 366 | return this.constructor.displayName + '#' + this.name; 367 | } 368 | 369 | function addAssertion() { 370 | this._result.addAssertion(); 371 | } 372 | 373 | p.run = run; 374 | p.addAssertion = addAssertion; 375 | p._filterException = _filterException; 376 | p.pause = pause; 377 | p.resume = resume; 378 | p.size = size; 379 | p.toString = toString; 380 | p.setUp = function() {}; 381 | p.tearDown = function() {}; 382 | })(TestCase.prototype); 383 | Evidence.TestCase = TestCase; 384 | function TestSuite(name, tests) { 385 | this.name = name; 386 | this._tests = []; 387 | if (tests) { 388 | this.push.apply(this, tests); 389 | } 390 | } 391 | 392 | TestSuite.displayName = 'TestSuite'; 393 | 394 | (function(p) { 395 | function run(result) { 396 | this._index = 0; 397 | this._result = result; 398 | result.startSuite(this); 399 | this.next(); 400 | return result; 401 | } 402 | 403 | function next() { 404 | var next = this._tests[this._index]; 405 | if (next) { 406 | this._index++; 407 | next.run(this._result); 408 | } else { 409 | this._result.stopSuite(this); 410 | if (this.parent) { 411 | this.parent.next(); 412 | } else { 413 | this._result.stop(new Date()); 414 | } 415 | } 416 | } 417 | 418 | function push() { 419 | for (var i = 0, length = arguments.length; i < length; i++) { 420 | var test = arguments[i]; 421 | test.parent = this; 422 | this._tests.push(test); 423 | } 424 | } 425 | 426 | function addTest(test) { 427 | test.parent = this; 428 | this._tests.push(test); 429 | } 430 | 431 | function addTests(tests) { 432 | for (var i = 0, length = tests.length; i < length; i++) { 433 | this.addTest(tests[i]); 434 | } 435 | } 436 | 437 | function size() { 438 | var tests = this._tests, 439 | length = tests.length, 440 | sum = 0; 441 | 442 | for (var i = 0; i < length; i++) { 443 | sum += tests[i].size(); 444 | } 445 | return sum; 446 | } 447 | 448 | function isEmpty() { 449 | return this.size() === 0; 450 | } 451 | 452 | function toString() { 453 | return this.name; 454 | } 455 | p.run = run; 456 | p.next = next; 457 | p.push = push; 458 | p.size = size; 459 | p.isEmpty = isEmpty; 460 | p.toString = toString; 461 | })(TestSuite.prototype); 462 | Evidence.TestSuite = TestSuite; 463 | function TestRunner() { 464 | } 465 | 466 | TestRunner.displayName = 'TestRunner'; 467 | 468 | (function(p) { 469 | function run(suite) { 470 | suite.parent = null; 471 | var result = this._makeResult(); 472 | result.start(new Date()); 473 | suite.run(result); 474 | return result; 475 | } 476 | 477 | function _makeResult() { 478 | return new TestResult(); 479 | } 480 | 481 | p.run = run; 482 | p._makeResult = _makeResult; 483 | })(TestRunner.prototype); 484 | Evidence.TestRunner = TestRunner; 485 | function TestLoader() { 486 | } 487 | 488 | TestLoader.displayName = 'TestLoader'; 489 | 490 | (function(p) { 491 | function loadTestsFromTestCase(testcaseClass) { 492 | var suite = new TestSuite(testcaseClass.displayName), 493 | props = this.getTestCaseNames(testcaseClass); 494 | for (var i=0; i < props.length; i++) { 495 | suite.push(new testcaseClass(props[i])); 496 | } 497 | return suite; 498 | } 499 | 500 | function loadTestsFromTestCases(testcases) { 501 | var suite = new TestSuite(getNameFromFile()); 502 | for (var i = 0; i < testcases.length; i++) { 503 | var testcase = testcases[i]; 504 | var subSuite = defaultLoader.loadTestsFromTestCase(testcase); 505 | if (!subSuite.isEmpty()) { suite.push(subSuite); } 506 | } 507 | return suite; 508 | } 509 | 510 | function getTestCaseNames(testcaseClass) { 511 | var results = [], 512 | proto = testcaseClass.prototype, 513 | prefix = this.testMethodPrefix; 514 | 515 | for (var property in proto) { 516 | if (property.indexOf(prefix) === 0) { 517 | results.push(property); 518 | } 519 | } 520 | return results.sort(); 521 | } 522 | 523 | function loadRegisteredTestCases() { 524 | return loadTestsFromTestCases(TestCase.subclasses); 525 | } 526 | 527 | p.loadTestsFromTestCase = loadTestsFromTestCase; 528 | p.loadRegisteredTestCases = loadRegisteredTestCases; 529 | p.loadTestsFromTestCases = loadTestsFromTestCases; 530 | p.testMethodPrefix = 'test'; 531 | p.getTestCaseNames = getTestCaseNames; 532 | 533 | })(TestLoader.prototype); 534 | Evidence.TestLoader = TestLoader; 535 | function AutoRunner() { 536 | if (global.console && global.console.log) { 537 | this.logger = Logger; 538 | } else if (Object.prototype.toString.call(global.environment) === '[object Environment]' && global.print) { 539 | this.logger = CommandLineLogger; 540 | } else { 541 | this.logger = PopupLogger; 542 | } 543 | this.autoRun = true; 544 | this.verbosity = Logger.INFO; 545 | this.runner = ConsoleTestRunner; 546 | } 547 | 548 | (function() { 549 | function run(options) { 550 | var autoRunner = new this(); 551 | options = options || autoRunner.retrieveOptions(); 552 | autoRunner.processOptions(options); 553 | if (autoRunner.autoRun) { autoRunner.run() }; 554 | } 555 | 556 | AutoRunner.run = run; 557 | AutoRunner.displayName = 'AutoRunner'; 558 | AutoRunner.LOGGERS = { 559 | console: Logger, 560 | popup: PopupLogger, 561 | command_line: CommandLineLogger 562 | }; 563 | 564 | AutoRunner.RUNNERS = { 565 | console: ConsoleTestRunner 566 | }; 567 | })(); 568 | 569 | (function(p) { 570 | function run() { 571 | var logger = new this.logger(this.verbosity), 572 | runner = new this.runner(logger), 573 | suite = defaultLoader.loadRegisteredTestCases(); 574 | if (suite._tests.length <= 1) { 575 | suite = suite._tests[0]; 576 | } 577 | return runner.run(suite); 578 | } 579 | 580 | function processQueryString(str) { 581 | var results = {}; 582 | str = (str + '').match(/^(?:[^?#]*\?)([^#]+?)(?:#.*)?$/); 583 | str = str && str[1]; 584 | 585 | if (!str) { return results; } 586 | 587 | var pairs = str.split('&'), 588 | length = pairs.length; 589 | if (!length) { return results; } 590 | 591 | for (var i = 0; i < length; i++) { 592 | var pair = pairs[i].split('='), 593 | key = decodeURIComponent(pair[0]), 594 | value = pair[1]; 595 | value = value ? decodeURIComponent(value) : true; 596 | results[key] = value; 597 | } 598 | return results; 599 | } 600 | 601 | function processArguments(args) { // RHINO 602 | var results = {}; 603 | 604 | for (var i = 0; i < args.length; i++) { 605 | var arg = args[i]; 606 | if (arg.indexOf('-') === 0) { 607 | var value = args[i + 1]; 608 | if (value && value.indexOf('-') !== 0) { 609 | i++; 610 | } else { 611 | value = true; 612 | } 613 | results[arg.substr(1)] = value; 614 | } 615 | } 616 | return results; 617 | } 618 | 619 | function retrieveOptions() { 620 | if (global.location) { 621 | return this.processQueryString(global.location); 622 | } 623 | if (global.arguments) { 624 | return this.processArguments(global.arguments); 625 | } 626 | return {}; 627 | } 628 | 629 | function processOptions(options) { 630 | for(var key in options) { 631 | var value = options[key]; 632 | switch(key) { 633 | case 'timeout': 634 | TestCase.defaultTimeout = global.parseFloat(value) * 1000; 635 | break; 636 | case 'run': 637 | this.autoRun = value === 'false' ? false : true; 638 | break; 639 | case 'logger': 640 | this.logger = AutoRunner.LOGGERS[value]; 641 | break; 642 | case 'verbosity': 643 | var i = global.parseInt(value); 644 | this.verbosity = global.isNaN(i) ? Logger[value] : i; 645 | break; 646 | case 'runner': 647 | this.runner = AutoRunner.RUNNERS[value]; 648 | break; 649 | } 650 | } 651 | } 652 | 653 | p.run = run; 654 | p.processQueryString = processQueryString; 655 | p.processArguments = processArguments; 656 | p.retrieveOptions = retrieveOptions; 657 | p.processOptions = processOptions; 658 | })(AutoRunner.prototype); 659 | Evidence.AutoRunner = AutoRunner; 660 | function TestResult() { 661 | this.testCount = 0; 662 | this.assertionCount = 0; 663 | this.skipCount = 0; 664 | this.skips = []; 665 | this.failureCount = 0; 666 | this.failures = []; 667 | this.errors = []; 668 | this.errorCount = 0; 669 | this.testCount = 0; 670 | } 671 | 672 | TestResult.displayName = 'TestResult'; 673 | 674 | (function(p) { 675 | function addAssertion() { 676 | this.assertionCount++; 677 | } 678 | 679 | function addSkip(testcase, reason) { 680 | this.skipCount++; 681 | this.skips.push(reason); 682 | } 683 | 684 | function addFailure(testcase, reason) { 685 | this.failureCount++; 686 | this.failures.push(reason); 687 | } 688 | 689 | function addError(testcase, error) { 690 | this.errorCount++; 691 | this.errors.push(error); 692 | } 693 | 694 | function startTest(testcase) { 695 | this.testCount++; 696 | } 697 | 698 | function stopTest(testcase) {} 699 | 700 | function pauseTest(testcase) {} 701 | 702 | function restartTest(testcase) {} 703 | 704 | function startSuite(suite) {} 705 | 706 | function stopSuite(suite) {} 707 | 708 | function start(t0) { 709 | this.t0 = t0; 710 | } 711 | 712 | function stop(t1) { 713 | this.t1 = t1; 714 | } 715 | 716 | function toString() { 717 | return this.testCount + ' tests, ' + 718 | this.assertionCount + ' assertions, ' + 719 | this.failureCount + ' failures, ' + 720 | this.errorCount + ' errors, ' + 721 | this.skipCount + ' skips'; 722 | } 723 | 724 | p.addAssertion = addAssertion; 725 | p.addSkip = addSkip; 726 | p.addFailure = addFailure; 727 | p.addError = addError; 728 | p.startTest = startTest; 729 | p.stopTest = stopTest; 730 | p.pauseTest = pauseTest; 731 | p.restartTest = restartTest; 732 | p.startSuite = startSuite; 733 | p.stopSuite = stopSuite; 734 | p.start = start; 735 | p.stop = stop; 736 | p.toString = toString; 737 | })(TestResult.prototype); 738 | Evidence.TestResult = TestResult; 739 | var Console = {}; 740 | 741 | function Logger(level) { 742 | if (typeof level !== 'undefined') { 743 | this.level = level; 744 | } 745 | } 746 | 747 | Logger.displayName = 'Logger'; 748 | Logger.LEVELS = ['NOTSET', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL']; 749 | Logger.CRITICAL = 5; 750 | Logger.ERROR = 4; 751 | Logger.WARN = 3; 752 | Logger.INFO = 2; 753 | Logger.DEBUG = 1; 754 | Logger.NOTSET = 0; 755 | 756 | (function(p) { 757 | function critical(template, params) { 758 | this.log(Logger.CRITICAL, template, params); 759 | } 760 | 761 | function error(template, params) { 762 | this.log(Logger.ERROR, template, params); 763 | } 764 | 765 | function warn(template, params) { 766 | this.log(Logger.WARN, template, params); 767 | } 768 | 769 | function info(template, params) { 770 | this.log(Logger.INFO, template, params); 771 | } 772 | 773 | function debug(template, params) { 774 | this.log(Logger.DEBUG, template, params); 775 | } 776 | 777 | function log(level, template, params) { 778 | level = level || Logger.NOTSET; 779 | var c = global.console; 780 | 781 | var method = Logger.LEVELS[level].toLowerCase(); 782 | if (method === 'critical') { method = 'error'; } 783 | method = (method in c) ? method : 'log'; 784 | 785 | if (level >= this.level) { 786 | if (params) { 787 | params = params.slice(0); 788 | params.unshift(template); 789 | c[method].apply(c, params); 790 | } else { 791 | c[method](template); 792 | } 793 | } 794 | } 795 | 796 | p.log = log; 797 | p.critical = critical; 798 | p.error = error; 799 | p.warn = warn; 800 | p.info = info; 801 | p.debug = debug; 802 | p.level = 0; 803 | })(Logger.prototype); 804 | Console.Logger = Logger; 805 | function PopupLogger(level) { 806 | Logger.call(this, level); 807 | } 808 | 809 | chain(PopupLogger, Logger); 810 | PopupLogger.displayName = 'PopupLogger'; 811 | 812 | (function(p) { 813 | var BASIC_STYLES = 'color: #333; background-color: #fff; font-family: monospace; border-bottom: 1px solid #ccc;'; 814 | var STYLES = { 815 | WARN: 'color: #000; background-color: #fc6;', 816 | ERROR: 'color: #f00; background-color: #fcc;', 817 | CRITICAL: 'color: #fff; background-color: #000;' 818 | }; 819 | 820 | function _cleanup(html) { 821 | return html.replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&').replace(/[\n\r]+/, '12 | See the browser console for results. 13 |
14 | 15 | 16 | 17 | 18 | 19 | 456 | 457 | 458 | --------------------------------------------------------------------------------