├── .travis.yml ├── Makefile ├── README.md ├── lib └── gerbil.js ├── package.json └── test ├── gerbil.html ├── gerbil.js └── test.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.4 4 | - 0.5 5 | - 0.6 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NODE = node 2 | 3 | all: 4 | @$(NODE) test/*.js 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gerbil 2 | 3 | [![Build Status](https://secure.travis-ci.org/elcuervo/gerbil.png?branch=master)](http://travis-ci.org/elcuervo/gerbil) 4 | 5 | ![Gerbil!](http://www.petsworld.co.uk/images/gerbil.jpg) 6 | 7 | _n_. Gerbils: Inquisitive, friendly animals that rarely bite, TDD for the rest of us 8 | 9 | Gerbil attemps to be an uber simple and minimalistic testing framework for javascript. 10 | 11 | ## Now with npm 12 | 13 | ```bash 14 | $ npm install gerbil 15 | ``` 16 | 17 | Or just include the .js and run tests within a normal browser. 18 | 19 | You can now execute the tests with node without depending on the browser 20 | 21 | ```javascript 22 | var scenario = require('gerbil').scenario; 23 | 24 | scenario("Testing with node", { 25 | "should work in a terminal": function(g){ 26 | g.assert(true); 27 | } 28 | }); 29 | 30 | // Or if you want to access some global Gerbil stuff 31 | 32 | var Gerbil = require('gerbil'); 33 | var scenario = Gerbil.scenario; 34 | ``` 35 | --- 36 | 37 | ## What's included? 38 | 39 | ### assert 40 | Good ol' assert, checks boolean. 41 | 42 | ### assertEqual 43 | Just like assert but checks types AND value. 44 | 45 | ### assertThrow 46 | Asserts an exception throw. 47 | 48 | ### assertType 49 | Asserts the type of the object to evaluate. 50 | 51 | ### pending 52 | Marks the test as pending. 53 | 54 | ### setTimeout 55 | Runs the test within a set time. 56 | 57 | ### async 58 | Runs async code. Eg. callbacks, timers. 59 | 60 | --- 61 | 62 | ## Example output 63 | 64 | ![Console Errors](http://elcuervo.co/images/posts/gerbil-tdd-for-the-rest-of-us/console-output.png?1) 65 | 66 | ![Console Errors](http://elcuervo.co/images/posts/gerbil-tdd-for-the-rest-of-us/error-output.png?2) 67 | 68 | ## Walkthrough 69 | 70 | ```javascript 71 | // Name the scenario you want to test and pass an object with your tests. 72 | scenario("Some useful stuff that needs to work", { 73 | // Reserved names are 'setup', 'before', 'after' and 'cleanup'. They define 74 | // the steps to be executed. 75 | // 76 | // Every test gets one parameter, this is the test suite itself. 77 | // Modifying 'this' will affect the context in the tests, it's useful when 78 | // using 'setup' to initialize some state. 79 | 'setup': function(g) { 80 | this.validName = 'Gerbil'; 81 | }, 82 | // Within the test 'this' gets the config defined in 'setup' 83 | 'should get the correct name': function(g) { 84 | g.assertEqual(this.validName, 'Gerbil'); 85 | }, 86 | 87 | // Test in the feature, useful to test future events or timers. 88 | 'in the future': function(g) { 89 | this.time = new Date().getTime(); 90 | 91 | g.setTimeout(function() { 92 | g.assert(new Date().getTime() > this.time); 93 | }, 1000); 94 | }, 95 | 96 | // Test async code. 97 | // 98 | // Using the async function you can control the status of the test. This is 99 | // really useful when you are testing callbacks. 100 | // But remember, it's your responsability to end() the test. 101 | 'should be able to test asyncronous code': function(g) { 102 | var asyncStuff = function() { 103 | this.callback = null; 104 | }; 105 | 106 | asyncStuff.prototype = { 107 | eventually: function(fn) { 108 | this.callback = fn; 109 | }, 110 | 111 | exec: function() { 112 | setTimeout(function(c) { 113 | c.callback(); 114 | }, 500, this); 115 | } 116 | }; 117 | 118 | g.async(function() { 119 | var async = new asyncStuff; 120 | async.eventually(function() { 121 | g.assert(true); 122 | // end() will end the current scenario and trigger a summary 123 | g.end(); 124 | }); 125 | async.exec(); 126 | }); 127 | } 128 | 129 | }); 130 | ``` 131 | 132 | ## Example 133 | 134 | ```javascript 135 | scenario("This is my scenario", { 136 | "setup": function() { 137 | // When scenario starts 138 | this.someThing = new Thing; 139 | }, 140 | "before": function() { 141 | // Before every test 142 | this.someThing.magic_magic(); 143 | }, 144 | "after": function() { 145 | // After every test 146 | this.someThing.clean(); 147 | }, 148 | "cleanup": function() { 149 | // When the scenario ends 150 | this.someThing = false; 151 | }, 152 | 153 | "MagicThing should have a length": function(g) { 154 | this.someThing.add(1); 155 | g.assertEqual(this.someThing.length, 1); 156 | }, 157 | 158 | "MagicThing should be valid": function(g) { 159 | g.assert(this.someThing.valid); 160 | } 161 | }); 162 | ``` 163 | --- 164 | 165 | ## Scenario config and global config. 166 | 167 | ```javascript 168 | var myCoolFormatter = { 169 | // Passing tests 170 | "ok": function(msg) {}, 171 | 172 | // Failing tests 173 | "fail": function(msg) {}, 174 | 175 | // Pending tests 176 | "pending": function(msg) {}, 177 | 178 | // The start of a scenario 179 | "scenario": function(msg) {}, 180 | 181 | // Report at the end of a scenario 182 | "summary": function(msg) {} 183 | }; 184 | 185 | scenario("Fancy scenario", { 186 | "config": function(c) { 187 | c.formatter = myCoolFormatter; 188 | }, 189 | "somewhere over the rainbow": function(g) { 190 | g.assert(false); 191 | } 192 | }); 193 | 194 | // Or if you want to affect every gerbil scenario 195 | 196 | Gerbil.globalConfig = { 197 | formatter: myCoolFormatter 198 | } 199 | ``` 200 | 201 | ### Callbacks 202 | 203 | Withing the config object you can add two types of callbacks, 'start' and 204 | 'finish'. This can help you trigger events after the scenario is finished or a 205 | nice sand clock when it starts. 206 | 207 | ```javascript 208 | scenario('configuration', { 209 | 'config': function(c) { 210 | c.start = function(object) {}; 211 | c.finish = function(results) {}; 212 | } 213 | }); 214 | 215 | // Of course you can define then globally: 216 | 217 | Gerbil.globalConfig = { 218 | start: function(object) {}, 219 | finish: function(results) {} 220 | }; 221 | ``` 222 | 223 | ## What's the catch? 224 | 225 | The results are only shown in the console, the one from console.log if you use 226 | it in a browser. 227 | Run it with an open inspector or define a custom formatter if you want prettier 228 | results. 229 | And in the bottom you will find the summary 230 | 231 | ![Browser tests](http://elcuervo.co/images/posts/gerbil-tdd-for-the-rest-of-us/browser-output.png?1) 232 | 233 | ## TODO 234 | 1. Get a gerbil as a pet 235 | 236 | ## Contributors 237 | * [foca](https://github.com/foca) 238 | * [Daniel Cadenas](https://github.com/dcadenas) 239 | -------------------------------------------------------------------------------- /lib/gerbil.js: -------------------------------------------------------------------------------- 1 | var Gerbil = function Gerbil(description, tests) { 2 | this.success = 0; 3 | this.pending = 0; 4 | this.failures = 0; 5 | this.count = 0; 6 | this.config = new Gerbil.Config(Gerbil.globalConfig); 7 | this.timeout = 0; 8 | this.queue = new Gerbil.Queue; 9 | this.results = new Gerbil.Queue; 10 | this.description = description; 11 | this.tests = tests; 12 | 13 | this.extractTest = function(key) { 14 | var test = this.tests[key]; 15 | delete this.tests[key]; 16 | return test || function() {}; 17 | }; 18 | 19 | this.execute = function(test, scope) { 20 | this.scope = scope; 21 | try { 22 | test.fn.call(scope, test); 23 | } catch(exception) { 24 | test.fails(exception); 25 | } finally { 26 | if(!test.isAsync) this.results.push(test); 27 | } 28 | }; 29 | 30 | this.ok = function(test) { 31 | this.success++; 32 | var message = Gerbil.format("{0} ({1} assertions)", [ 33 | test.name, test.assertions 34 | ]); 35 | 36 | test.scenario.config.formatter.ok(message); 37 | }; 38 | 39 | this.fail = function(test) { 40 | this.failures++; 41 | var message = Gerbil.format("{0} - assertion number {1} failed - {2}", [ 42 | test.name, test.assertions + 1, test.message 43 | ]); 44 | 45 | test.scenario.config.formatter.fail(message); 46 | }; 47 | 48 | this.postergate = function(test) { 49 | this.pending++; 50 | test.scenario.config.formatter.pending(test.message); 51 | }; 52 | 53 | this.enqueue = function() { 54 | var configFn = this.extractTest("config"); 55 | configFn(this.config); 56 | 57 | this.setup = this.extractTest("setup"); 58 | this.before = this.extractTest("before"); 59 | this.after = this.extractTest("after"); 60 | this.cleanup = this.extractTest("cleanup"); 61 | 62 | for (var key in this.tests) { 63 | this.queue.push(new Gerbil.Test(key, this.tests[key], this)); 64 | this.count++; 65 | } 66 | return this; 67 | }; 68 | 69 | this.check = function(scenario) { 70 | if(scenario.count !== scenario.results.length()) { 71 | scenario.timeout += 100; 72 | setTimeout(scenario.check, scenario.timeout, scenario); 73 | } else { 74 | scenario.summary(); 75 | } 76 | }; 77 | 78 | this.run = function() { 79 | var test = false; 80 | var scope = {}; 81 | 82 | if(typeof this.config.start === 'function') { 83 | var obj = { 84 | description: this.description, 85 | numberOfTests: this.queue.length() 86 | }; 87 | 88 | this.config.start(obj); 89 | } 90 | 91 | try { 92 | this.setup.call(scope); 93 | do { 94 | test = this.queue.pull(); 95 | if(test) { 96 | this.before.call(scope, test); 97 | 98 | test.measure(); 99 | this.execute(test, scope); 100 | test.measure(); 101 | 102 | this.after.call(scope, test); 103 | } 104 | } while(test); 105 | this.cleanup.call(scope); 106 | } catch(exception) { 107 | throw Gerbil.Error({ message: exception }); 108 | } finally { 109 | setTimeout(this.check, this.timeout, this); 110 | } 111 | 112 | return this; 113 | }; 114 | 115 | this.summary = function() { 116 | var result = false; 117 | var assertions = 0; 118 | var elapsedTime = 0; 119 | 120 | this.config.formatter.scenario(this.description); 121 | 122 | do { 123 | test = this.results.pull(); 124 | if(test) { 125 | if(test.isPending) { 126 | this.postergate(test); 127 | } else { 128 | assertions += test.assertions; 129 | elapsedTime += test.time; 130 | test.failed ? this.fail(test) : this.ok(test); 131 | } 132 | } 133 | } while(test); 134 | 135 | var results = { 136 | description: this.description, 137 | pass: this.success, 138 | fail: this.failures, 139 | pending: this.pending, 140 | total: this.count, 141 | assertions: assertions, 142 | time: elapsedTime 143 | }; 144 | 145 | this.config.formatter.summary(results); 146 | 147 | typeof this.config.finish == 'function' && this.config.finish(results); 148 | }; 149 | 150 | this.enqueue(); 151 | }; 152 | 153 | Gerbil.IS_NODE = !!(typeof module !== 'undefined' && module.exports); 154 | 155 | Gerbil.format = function(s, args) { 156 | var re = /\{([^}]+)\}/g; 157 | return s.replace(re, function(_, match){ return args[match]; }); 158 | }; 159 | 160 | Gerbil.Error = function GerbilError(options) { 161 | if(options.args) { 162 | options.message = Gerbil.format(options.message, options.args); 163 | } 164 | var error = new Error(options.message); 165 | if(options.stack) error.stack = options.stack; 166 | 167 | return error; 168 | }; 169 | 170 | Gerbil.Queue = function GerbilQueue() { 171 | this.queue = []; 172 | this.offset = 0; 173 | 174 | this.length = function() { 175 | return this.queue.length - this.offset; 176 | }; 177 | 178 | this.push = function(item) { 179 | this.queue.push(item); 180 | }; 181 | 182 | this.pull = function() { 183 | if (this.queue.length === 0) return false; 184 | var item = this.queue[this.offset]; 185 | 186 | if (++this.offset * 2 >= this.queue.length) { 187 | this.queue = this.queue.slice(this.offset); 188 | this.offset = 0; 189 | } 190 | return item; 191 | }; 192 | }; 193 | 194 | Gerbil.Test = function GerbilTest(name, test, scenario) { 195 | this.name = name; 196 | this.scenario = scenario; 197 | this.fn = test; 198 | this.assertions = 0; 199 | this.failed = false; 200 | this.isAsync = false; 201 | this.isPending = false; 202 | this.message = null; 203 | this.time = null; 204 | 205 | this.fails = function(exception) { 206 | this.failed = true; 207 | this.message = exception.stack; 208 | }; 209 | 210 | this.measure = function() { 211 | var milliseconds = new Date().getTime()/1000; 212 | this.time = this.time === null ? milliseconds : milliseconds - this.time; 213 | }; 214 | }; 215 | 216 | Gerbil.Test.prototype = { 217 | constructor: Gerbil.Test, 218 | 219 | setTimeout: function(fn, milliseconds) { 220 | var context = this.scenario.scope; 221 | this.scenario.timeout += milliseconds; 222 | 223 | return setTimeout(function() { 224 | fn.apply(context); 225 | }, milliseconds); 226 | }, 227 | 228 | pending: function(message) { 229 | this.isPending = true; 230 | this.message = this.name; 231 | if(message) this.message += ' (' + message + ')'; 232 | }, 233 | 234 | async: function(fn) { 235 | this.isAsync = true; 236 | try { 237 | fn.call(this.scenario.scope); 238 | } catch(exception) { 239 | this.fails(exception); 240 | this.end(); 241 | } 242 | }, 243 | 244 | end: function() { 245 | if(this.isAsync) this.scenario.results.push(this); 246 | }, 247 | 248 | assert: function(expectation) { 249 | if(!expectation) { 250 | throw Gerbil.Error({ message: "Assertion Failed" }); 251 | } else { 252 | this.assertions++; 253 | } 254 | }, 255 | 256 | assertThrow: function(expectedError, fn) { 257 | this.assertions++; 258 | var errorMessage = false; 259 | try { 260 | fn(); 261 | errorMessage = expectedError.name + " was expected but not raised."; 262 | } catch(exception) { 263 | if (!(exception instanceof expectedError)) { 264 | errorMessage = expectedError.name + " was expected but " + exception.name + " was raised."; 265 | } 266 | } 267 | if (errorMessage) throw Gerbil.Error({message: errorMessage}); 268 | }, 269 | 270 | assertType: function(type, object) { 271 | this.assertions++; 272 | if(object.constructor != type) { 273 | var message = 'Expected to be ' + type.name + ' but ' + object.constructor.name + ' was found.'; 274 | throw Gerbil.Error({ message: message }); 275 | } 276 | }, 277 | 278 | assertEqual: function(first, second) { 279 | if(first === undefined || second === undefined) { 280 | throw Gerbil.Error({ 281 | message: "attr2 = {0} ({1}) and attr2 = {2} ({3})", 282 | args: [first, typeof first, second, typeof second] 283 | }); 284 | } 285 | 286 | if(typeof first != typeof second) { 287 | throw Gerbil.Error({ 288 | message: "Different type {0} vs {1}", 289 | args: [typeof first, typeof second] 290 | }); 291 | } 292 | 293 | this.assertions++; 294 | var errorMessage = "Not equal {0} != {1}"; 295 | 296 | switch(first.constructor) { 297 | case Array: 298 | if (first.length != second.length) { 299 | throw Gerbil.Error({ message:"Different Lengths" }); 300 | } 301 | for (var i = 0; i < first.length; i++) { 302 | if (first[i] != second[i]) { 303 | throw Gerbil.Error({ 304 | message: errorMessage, 305 | args: [first[i], second[i]] 306 | }); 307 | } 308 | } 309 | break; 310 | case String: 311 | case Number: 312 | if (first != second) { 313 | throw Gerbil.Error({ 314 | message: errorMessage, 315 | args: [first, second] 316 | }); 317 | } 318 | break; 319 | default: 320 | break; 321 | } 322 | } 323 | }; 324 | 325 | Gerbil.Console = { 326 | pretty: { 327 | log: function(msg) { return console.log("\033[32m" + msg + "\033[0m"); }, 328 | info: function(msg) { return console.info("\033[34m" + msg + "\033[0m"); }, 329 | warn: function(msg) { return console.warn("\033[33m" + msg + "\033[0m"); }, 330 | error: function(msg) { return console.error("\033[31m" + msg + "\033[0m"); } 331 | }, 332 | 333 | simple: console 334 | }; 335 | 336 | Gerbil.console = Gerbil.IS_NODE ? Gerbil.Console.pretty : Gerbil.Console.simple; 337 | 338 | Gerbil.formatter = { 339 | ok: function(msg) { 340 | Gerbil.console.log(" * " + msg); 341 | }, 342 | fail: function(msg) { 343 | Gerbil.console.error(" x " + msg); 344 | }, 345 | pending: function(msg) { 346 | Gerbil.console.warn(" ! " + msg); 347 | }, 348 | scenario: function(msg) { 349 | Gerbil.console.info("== Running " + msg + " =="); 350 | }, 351 | summary: function(summary) { 352 | var summaryString = Gerbil.format("All tests completed for {0}: {1} passed, {2} failed, {3} pending, of {4} tests ({5} assertions) in {6} s", [ 353 | summary.description, 354 | summary.pass, 355 | summary.fail, 356 | summary.pending, 357 | summary.total, 358 | summary.assertions, 359 | summary.time 360 | ]); 361 | Gerbil.console.warn(summaryString); 362 | Gerbil.console.info(""); 363 | } 364 | }; 365 | 366 | Gerbil.Config = function(globalConfig) { 367 | this.formatter = globalConfig && globalConfig.formatter || Gerbil.formatter; 368 | this.prototype = globalConfig; 369 | }; 370 | 371 | Gerbil.scenario = function scenario(description, tests) { 372 | return new Gerbil(description, tests).run(); 373 | }; 374 | 375 | if(Gerbil.IS_NODE) { 376 | module.exports = Gerbil; 377 | } else { 378 | var scenario = Gerbil.scenario; 379 | } 380 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gerbil", 3 | "description": "Gerbil: Inquisitive, friendly animals that rarely bite, TDD for the rest of us", 4 | "version": "0.3.8", 5 | "author": "elCuervo ", 6 | "keywords": ["tdd", "testing", "gerbil", "simple"], 7 | "directories": {"lib": "./lib", "test": "./test"}, 8 | "repositories": [ 9 | { 10 | "type": "git", 11 | "url": "git://github.com/elcuervo/gerbil.git" 12 | } 13 | ], 14 | "main": "./lib/gerbil.js", 15 | "scripts": { 16 | "test": "node test/gerbil.js" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/gerbil.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 76 | 82 | 83 | 84 |
85 |
86 | 87 | 88 | -------------------------------------------------------------------------------- /test/gerbil.js: -------------------------------------------------------------------------------- 1 | if(typeof module != 'undefined') 2 | var scenario = require('../lib/gerbil.js').scenario; 3 | 4 | scenario("Gerbil - Assertions", { 5 | "should be able to assert": function(g){ 6 | g.assert(true); 7 | g.assert(1 == 1); 8 | g.assert(true != false); 9 | }, 10 | 11 | "should be able to assert a false statement": function(g){ 12 | g.assertThrow(Error, function(){ 13 | g.assert(false) 14 | }) 15 | }, 16 | 17 | "should fail when an assertThrow block doesn't raise an exception": function(g){ 18 | g.assertThrow(Error, function(){ 19 | g.assertThrow(Error, function(){ 20 | //we are not raising anything 21 | }) 22 | }); 23 | }, 24 | 25 | "should be able to validate the type of an object": function(g) { 26 | g.assertType(Function, function() {}); 27 | g.assertType(Number, 42); 28 | g.assertType(String, 'Gerbil'); 29 | 30 | g.assertThrow(Error, function() { 31 | g.assertType(Function, 42); 32 | }); 33 | }, 34 | 35 | "should rise an error if expected and thrown errors don't match": function(g) { 36 | function CustomError1() {} 37 | function CustomError2() {} 38 | 39 | g.assertThrow(Error, function() { 40 | g.assertThrow(CustomError1, function(){ 41 | throw new CustomError2(); 42 | }); 43 | }); 44 | }, 45 | 46 | }); 47 | 48 | scenario("Gerbil - setTimeout", { 49 | 'setup': function() { 50 | this.name = 'timeout'; 51 | }, 52 | "should access gerbil test scenario": function(g){ 53 | var a = 1; 54 | g.setTimeout(function(){ 55 | a++; 56 | g.assertEqual(a, 3); 57 | }, 1000); 58 | a++; 59 | }, 60 | 61 | 'should access context': function(g) { 62 | g.setTimeout(function() { 63 | g.assertEqual(this.name, 'timeout'); 64 | }, 1000); 65 | } 66 | }); 67 | 68 | scenario("Gerbil - special methods", { 69 | 'pending': function(g) { 70 | return g.pending("This is pending somehow"); 71 | g.assert(false); 72 | } 73 | }); 74 | 75 | scenario('Gerbil - context access for tests', { 76 | 'setup': function(g) { 77 | this.value = 1; 78 | }, 79 | 'should access context': function(g) { 80 | g.assertEqual(this.value, 1); 81 | } 82 | }); 83 | 84 | scenario('Gerbil - asyncronous code', { 85 | 'setup': function() { 86 | this.context = true; 87 | }, 88 | 89 | 'should be able to test asyncronous code': function(g) { 90 | var asyncStuff = function() { 91 | this.callbacks = {}; 92 | this.numer = null; 93 | 94 | this.random = function() { 95 | return Math.floor(Math.random()*11); 96 | } 97 | }; 98 | asyncStuff.prototype = { 99 | eventually: function(fn) { 100 | this.callback = fn; 101 | }, 102 | 103 | exec: function() { 104 | setTimeout(function(c) { 105 | c.callback(); 106 | }, 500, this); 107 | } 108 | }; 109 | 110 | g.async(function() { 111 | var async = new asyncStuff; 112 | async.eventually(function() { 113 | g.assert(true); 114 | g.end(); 115 | }); 116 | async.exec(); 117 | }); 118 | }, 119 | 120 | 'should not lose context during async execution': function(g) { 121 | g.async(function() { 122 | g.assertEqual(this.context, true); 123 | g.end(); 124 | }); 125 | } 126 | 127 | }); 128 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | if(typeof module != 'undefined') { 2 | var Gerbil = require('../lib/gerbil'); 3 | var scenario = Gerbil.scenario; 4 | } 5 | 6 | scenario("Validate some stuff", { 7 | "before": function() { 8 | this.a = 2; 9 | }, 10 | 11 | "this is a pending test": function(g) { 12 | return g.pending("TODO"); 13 | }, 14 | 15 | "this is another pending test": function(g) { 16 | return g.pending(); 17 | }, 18 | 19 | "test": function(g) { 20 | g.assert(true); 21 | g.assertEqual(1); 22 | }, 23 | 24 | "cuteness": function(g) { 25 | g.assert(true); 26 | }, 27 | 28 | "take a long time": function(g) { 29 | for(var i = 0; i < 10000000; i++) {} 30 | g.assertEqual(i, 10000000); 31 | g.assertType(Function, 23); 32 | }, 33 | 34 | "show error on async code": function(g) { 35 | g.async(function() { 36 | g.assert(false); 37 | }); 38 | }, 39 | 40 | "show improved stack trace": function(g) { 41 | var f = function() { 42 | throw Error(); 43 | }; 44 | g.assert(f()); 45 | } 46 | }); 47 | --------------------------------------------------------------------------------