From: dsc Date: Mon, 26 Mar 2012 22:50:43 +0000 (-0700) Subject: Adds Cascade class, tests. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=3e7a8eafbb5f97ee24415e375fe2b44b8ee53b6e;p=limn-bak.git Adds Cascade class, tests. --- diff --git a/Cokefile b/Cokefile index 1e04534..3064b0e 100644 --- a/Cokefile +++ b/Cokefile @@ -5,6 +5,9 @@ require 'buildtools/tasks' MODULE_LINK = 'node_modules/kraken' EXPRESS_DEP_MIME = 'node_modules/express/node_modules/mime' + + + task \link 'Link package source to node_modules so the name resolves correctly' -> # Browser-based require doens't support relative requires, but things # like `require 'kraken/utils'` rarely work in node without this hack. @@ -23,36 +26,65 @@ task \install 'Install project dependencies.' -> task \setup 'Ensure project is set up for development.' -> invoke \install invoke \link - invoke \update_version + task \server 'Start dev server' -> invoke \setup + invoke \update_version say '' run 'lib/server/server.co' + task \build 'Build coco sources' -> invoke \setup coco <[ -bjc package.co ]> + task \test 'Rebuild test files and run tests' -> invoke \setup - tests = glob.globSync 'test/*.co' - tests.forEach (file) -> - js = file.replace '.co', '.js' - fs.unlinkSync js if exists js - coco [ '-bc', file ] - sh 'expresso' + invoke \cleanup_tests + + # Compile tests to JS so Expresso finds them + say "Compiling tests...".white.bold + glob 'test/**/*.co', {+sync} .forEach -> coco [ '-bc', it ] + say 'ok.\n' + + say "Running tests...".white.bold + err <- sh 'expresso', {-die} + say "#{err and 'yep' or 'ok'}.\n" + + # Clean up JS turds + invoke \cleanup_tests + + +task \cleanup_tests 'Removes compiled tests' -> + # XXX: erp. only works if no .js files by default :P + # say "Cleaning up old test files...".white.bold + # js_files = glob 'test/**/*.js', {+sync} + # for js of js_files.filter( -> exists it ) + # say "unlink #js" + # fs.unlinkSync js + # say 'ok.\n' + + glob 'test/**/*.co', {+sync} + .map -> it.replace('.co', '.js') + .filter exists + .forEach fs.unlinkSync task \clean 'Clean up environment and artifacts' -> - remove [MODULE_LINK, EXPRESS_DEP_MIME, 'var', 'tmp/dest'], true + invoke \cleanup_tests + remove [MODULE_LINK, EXPRESS_DEP_MIME, 'var', 'tmp/dist'], true + + task \source_list 'Print a list of the source file paths.' -> - invoke \link + invoke \setup {sources} = require 'kraken/server/view-helpers' say sources("www/modules.yaml", 'dev').join '\n' # task \dist 'Assemble a distribution package for deploy' -> +# invoke \cleanup_tests # ... diff --git a/lib/util/cascade.co b/lib/util/cascade.co index 6b3ade0..3fa01a2 100644 --- a/lib/util/cascade.co +++ b/lib/util/cascade.co @@ -27,25 +27,31 @@ class Cascade * @constructor */ (data={}, lookups=[]) -> - @_data = {} import data - @_lookups = [@_data].concat lookups.slice() + @_data = data + @_lookups = [@_data].concat lookups /** - * @returns {Cascade} A copy of the lookup chain. + * @returns {Cascade} A copy of the data and lookup chain. */ clone: -> - new Cascade @_data, @_lookups + new Cascade {} import @_data, @_lookups.slice() - ### Lookups ### + ### Data & Lookups ### + + setData: (data) -> + @_data = @_lookups[0] = data + this /** * Adds a new lookup dictionary to the chain. * @returns {this} */ addLookup: (dict) -> + return this unless dict? + throw new Error "Lookup dictionary must be an object! dict=#dict" unless dict @_lookups.push dict this @@ -54,9 +60,20 @@ class Cascade * @returns {this} */ removeLookup: (dict) -> - _.remove @_lookups, dict + _.remove @_lookups, dict if dict this + /** + * @returns {Boolean} Whether the value at `key` belongs to this object or exists in the cascade. + */ + isOwnValue: (key) -> + @_data[key] is not void + + /** + * @returns {Number} Number of lookup dictionaries. + */ + size: -> + @_lookups.length @@ -66,8 +83,7 @@ class Cascade * @returns {Boolean} Whether there is a value at the given key. */ has : (key) -> - (@get key, undefined) is not undefined - + (@get key, void) is not void /** * @returns {*} First value for the given key found in the lookup chain, @@ -75,18 +91,41 @@ class Cascade */ get : (key, def) -> for data of @_lookups - val = _.getNested data, key, undefined - return val if val is not undefined + if typeof data.get is 'function' + val = data.get key, void + else + val = _.getNested data, key, void + return val if val is not void def /** + * Sets a key to a value, accepting nested keys and creating intermediary objects as necessary. + * @public + * @name set * @param {String} key Key to set. * @param {*} val Non-`undefined` value to set. * @returns {this} */ - set : (key, val) -> - throw new Error("Value and key cannot be undefined!") unless key and val is not undefined - _.setNested @_data, key, val + /** + * @public + * @name set + * @param {Object} values Map of KV pairs to set. No value may be `undefined`. + * @returns {this} + */ + set : (values) -> + # Handle @set(k, val) + if arguments.length > 1 and typeof values is 'string' + [key, val] = arguments + throw new Error("Value and key cannot be undefined!") if not key or val is void + values = { "#key": val } + + # Trailing `true` in call to `set()` and `setNested()` is to ensure the + # creation of missing intermediate objects. + if typeof @_data.set is 'function' + for key, val in values then @_data.set key, val, true + else + for key, val in values then _.setNested @_data, key, val, true + this @@ -95,8 +134,8 @@ class Cascade * does not cascade. * @returns {undefined|*} If found, returns the old value, and otherwise `undefined`. */ - del: (key) -> - _.unsetNested @_data, key, false + unset: (key) -> + _.unsetNested @_data, key @@ -104,7 +143,7 @@ class Cascade extend : (...args) -> for o of args - for k,v in o then @set k, v + for k, v in o then @set k, v this toObject: -> diff --git a/lib/util/index.co b/lib/util/index.co index 285a8f2..2cc39e1 100644 --- a/lib/util/index.co +++ b/lib/util/index.co @@ -16,17 +16,5 @@ backbone = require 'kraken/util/backbone' parser = require 'kraken/util/parser' -## Debug -_.dump = (o, label='dump') -> - if not _.isArray(o) and _.isObject(o) - console.group label - for k, v in o - console.log "#k:", v - console.groupEnd() - else - console.log label, o - o - - exports import { root, _, op, backbone, parser, } # exports import { root, _, op, HashSet, BitString, crc32, parser, } diff --git a/test/index.co b/test/index.co new file mode 100644 index 0000000..33541de --- /dev/null +++ b/test/index.co @@ -0,0 +1,4 @@ +coco = require 'coco' +util = require './util' + +exports import util diff --git a/test/util/cascade-test.co b/test/util/cascade-test.co new file mode 100644 index 0000000..4524bf2 --- /dev/null +++ b/test/util/cascade-test.co @@ -0,0 +1,94 @@ +assert = require 'assert' + +_ = require 'kraken/underscore' +Cascade = require 'kraken/util/cascade' + + +assertEqual = (got, expected, msg) -> + assert.equal got, expected, "#msg:\t Expected: #expected;\tGot: #got" + +exports.basicCascade = -> + a = { a:1 } + b = { b:2 } + c = { c:3 } + + c1 = new Cascade + assertEqual c1.get('a'), void, "[c1] Primary data lookup before set" + c1.set 'a', 1 + assertEqual c1.get('a'), 1, "[c1] Primary data lookup (after set)" + assertEqual c1.get('b'), void, "[c1] Cascade lookup (depth=1, before set)" + c1.addLookup b + assertEqual c1.get('a'), 1, "[c1] Primary data lookup (after lookup added)" + assertEqual c1.get('b'), 2, "[c1] Cascade lookup (depth=1, first)" + assertEqual c1.get('c'), void, "[c1] Cascade lookup (depth=2, unset)" + c1.addLookup c + assert.ok c1.has('a'), "[c1] Cascade has 'a' key (depth=0)" + assert.ok c1.has('b'), "[c1] Cascade has 'b' key (depth=1)" + assert.ok c1.has('c'), "[c1] Cascade has 'c' key (depth=2)" + assertEqual c1.get('a'), 1, "[c1] Primary data lookup (after lookup #2 added)" + assertEqual c1.get('b'), 2, "[c1] Cascade lookup (depth=1, second)" + assertEqual c1.get('c'), 3, "[c1] Cascade lookup (depth=2, first)" + c1.removeLookup b + assertEqual c1.get('a'), 1, "[c1] Primary data lookup (after lookup b removed)" + assertEqual c1.get('b'), void, "[c1] Cascade lookup (depth=1, third)" + assertEqual c1.get('c'), 3, "[c1] Cascade lookup (depth=2, removed)" + c1.removeLookup c + assertEqual c1.get('a'), 1, "[c1] Primary data lookup (after lookup c removed)" + assertEqual c1.get('b'), void, "[c1] Cascade lookup (depth=1, removed)" + c1.unset 'a' + assertEqual c1.get('a'), void, "[c1] Primary data lookup (after removed)" + + c2 = new Cascade a, [b, c] + assert.notStrictEqual c1, c2, "[c2] Different Cascades should differ" + + assert.ok c2.has('a'), "[c2] Cascade has 'a' key (depth=0)" + assert.ok c2.has('b'), "[c2] Cascade has 'b' key (depth=1)" + assert.ok c2.has('c'), "[c2] Cascade has 'c' key (depth=2)" + assertEqual c2.get('a'), 1, "[c2] Primary data lookup" + assertEqual c2.get('b'), 2, "[c2] Cascade lookup (depth=1, first)" + assertEqual c2.get('c'), 3, "[c2] Cascade lookup (depth=2, first)" + c2.removeLookup b + assertEqual c2.get('a'), 1, "[c2] Primary data lookup (after lookup b removed)" + assertEqual c2.get('b'), void, "[c2] Cascade lookup (depth=1, second)" + assertEqual c2.get('c'), 3, "[c2] Cascade lookup (depth=2, removed)" + c2.removeLookup c + assertEqual c2.get('a'), 1, "[c2] Primary data lookup (after lookup c removed)" + assertEqual c2.get('b'), void, "[c2] Cascade lookup (depth=1, removed)" + c2.unset 'a' + assertEqual c2.get('a'), void, "[c2] Primary data lookup (after removed)" + + +exports.nestedKeys = -> + o = {} + a = + lol: 'cats' + hat: false + foo: bar:1 + b = + lol: 'clowns' + hat: fez:true + baz: feh:2 + bats: 13 + + c = new Cascade o, [a, b] + + assertEqual c.get('lol'), 'cats', "Shadow c.get('lol')" + assertEqual c.get('bats'), 13, "Simple cascade c.get('bats')" + assertEqual c.get('foo.bar'), 1, "Nested c.get('foo.bar')" + assertEqual c.get('baz.feh'), 2, "Nested cascade c.get('bar.feh')" + + assertEqual c.get('hat'), false, "Shadow non-cascade c.get('hat')" + assertEqual c.get('hat.fez'), true, "Unshadow due to cascade c.get('hat.fez')" + + c.set 'hat.fez', 'red' + assertEqual c.get('hat.fez'), 'red', "After nested set c.get('hat.fez')" + assertEqual o.hat?.fez, 'red', "After nested set o.hat?.fez" + assertEqual b.hat?.fez, true, "After nested set b.hat?.fez" + + c.unset 'hat.fez' + assertEqual c.get('hat.fez'), true, "After unset c.get('hat.fez')" + assertEqual o.hat?.fez, void, "After unset o.hat?.fez" + + + + diff --git a/test/util/index.co b/test/util/index.co new file mode 100644 index 0000000..1cbd9af --- /dev/null +++ b/test/util/index.co @@ -0,0 +1,2 @@ +cascade = require './cascade-test' +exports import cascade diff --git a/www/modules.yaml b/www/modules.yaml index 3ade6ac..f3c8187 100644 --- a/www/modules.yaml +++ b/www/modules.yaml @@ -13,23 +13,33 @@ dev: - es5-shim.min - modernizr.min - json2.min - - jquery.min + + - jquery - jquery.history.min - jquery.hotkeys.min - jquery.isotope.min - # - jquery.tipsy.min # handled by bootstrap now? - - spin.min - - jquery.spin.min + + # handled by bootstrap now? + # - jquery.tipsy.min + + # - spin.min + # - jquery.spin.min - bootstrap.min + + # Browserify must come before any .mod files - browserify - # - require - - underscore.mod.min - - underscore.string.mod.min - - backbone.mod.min - - backbone.nested.mod.min + + - underscore.mod + - underscore.string.mod + + - backbone.mod + - backbone.nested.mod + - synapse.mod + - showdown.mod.min - jade.runtime.min - - dygraph.min + + - dygraph - suffix: .mod.js paths: