UNPKG

hinoki

Version:

sane, simple dependency injection and more

423 lines (356 loc) 9.68 kB
test = require 'tape' Promise = require 'bluebird' helfer = require 'helfer' hinoki = require '../lib/hinoki' test 'get value', (t) -> source = -> t.ok false lifetime = x: 1 hinoki(source, lifetime, 'x').then (x) -> t.equal x, 1 t.end() test 'get null value', (t) -> source = -> t.ok false lifetime = x: null hinoki(source, lifetime, 'x').then (x) -> t.ok helfer.isNull x t.end() test 'sync get from factory without dependencies', (t) -> source = hinoki.source x: -> 1 lifetime = {} hinoki(source, lifetime, 'x').then (x) -> t.equal x, 1 t.equal lifetime.x, 1 t.end() test 'sync get null from factory without dependencies', (t) -> source = hinoki.source x: -> null lifetime = {} hinoki(source, lifetime, 'x').then (x) -> t.ok helfer.isNull x t.ok helfer.isNull lifetime.x t.end() test 'async get from factory without dependencies', (t) -> source = hinoki.source x: -> Promise.resolve 1 lifetime = {} hinoki(source, lifetime, 'x').then (x) -> t.equal x, 1 t.equal lifetime.x, 1 t.end() test 'async get null from factory without dependencies', (t) -> source = hinoki.source x: -> Promise.resolve null lifetime = {} hinoki(source, lifetime, 'x').then (x) -> t.ok helfer.isNull x t.ok helfer.isNull lifetime.x t.end() test 'sync get with dependencies', (t) -> source = hinoki.source x: (y) -> 1 + y y: -> 1 lifetime = {} hinoki(source, lifetime, 'x').then (x) -> t.equal x, 2 t.deepEqual lifetime, x: 2 y: 1 t.end() test 'sync get null with dependencies', (t) -> source = hinoki.source x: (y) -> null y: -> 1 lifetime = {} hinoki(source, lifetime, 'x').then (x) -> t.ok helfer.isNull x t.ok helfer.isNull lifetime.x t.end() test 'injectable is injected correctly', (t) -> source = hinoki.source a: -> 0 b: -> 1 c: (a, b) -> a + b d: (b, c) -> b + c e: (c, d) -> c + d f: (d, e) -> d + e g: (e, f) -> e + f h: (f, g) -> f + g lifetime = {} hinoki(source, lifetime, (a, b, c, d, e, f, g, h) -> [a, b, c, d, e, f, g, h] ).then (fibonacci) -> t.deepEqual fibonacci, [0, 1, 1, 2, 3, 5, 8, 13] t.end() test 'a value is cached in the last lifetime that contains values it directly or indirectly depends on', (t) -> req = {} res = {} params = {} next = -> currentUser = {} env = {} databaseConnection = {} isUserAllowedToAccessResource = -> selectUser = -> databaseUrl = 'kjsldkfjd' source = hinoki.source currentUser: (req, selectUser) -> currentUser isCurrentUserAllowedToAccessResource: (params, currentUser) -> true env: -> env databaseUrl: (env) -> databaseUrl databaseConnection: (databaseUrl) -> databaseConnection isUserAllowedToAccessResource: (databaseConnection) -> isUserAllowedToAccessResource selectUser: (databaseConnection) -> selectUser applicationLifetime = {} requestLifetime = req: req res: res middlewareLifetime = params: params next: next lifetimes = [ applicationLifetime requestLifetime middlewareLifetime ] hinoki source, lifetimes, (isCurrentUserAllowedToAccessResource) -> t.deepEqual applicationLifetime, env: env databaseUrl: databaseUrl databaseConnection: databaseConnection selectUser: selectUser t.deepEqual requestLifetime, req: req res: res currentUser: currentUser t.deepEqual middlewareLifetime, params: params next: next isCurrentUserAllowedToAccessResource: true t.end() test 'a factory is called no more than once', (t) -> callsTo = a: 0 b: 0 c: 0 d: 0 source = hinoki.source a: (b, c) -> callsTo.a++ Promise.delay(40, b + c) b: (d) -> callsTo.b++ Promise.delay(20, d + 1) c: (d) -> callsTo.c++ Promise.delay(30, d + 2) d: -> callsTo.d++ Promise.delay(10, 10) lifetime = {} hinoki(source, lifetime, 'a').then (a) -> t.equal callsTo.a, 1 t.equal callsTo.b, 1 t.equal callsTo.c, 1 t.equal callsTo.d, 1 t.equal a, 23 t.end() test 'promises awaiting resolution are cached and reused', (t) -> t.plan 8 object = {} source = hinoki.source a: -> # here a new object is created Promise.delay 10, object lifetime = {} p1 = hinoki(source, lifetime, 'a') t.ok lifetime.a? # the first promise has one step more which handles caching # and cleanup of promise caching t.notEqual p1, lifetime.a # ask again p2 = hinoki(source, lifetime, 'a') t.equal p2, lifetime.a p3 = hinoki(source, lifetime, 'a') t.equal p3, lifetime.a Promise.all([p1, p2, p3]).then ([a1, a2, a3]) -> # three pointers to the same object! t.equal a1, object t.equal a1, a2 t.equal a2, a3 t.equal lifetime.a, object t.end() test 'all dependent promises are created without interleaving', (t) -> t.plan 20 a = {} source = hinoki.source a: -> t.ok lifetime.a? t.ok lifetime.b? t.ok lifetime.c? Promise.delay 10, a b: (a) -> t.ok lifetime.b? t.ok lifetime.c? Promise.delay 10, {a: a} c: (b) -> t.ok lifetime.c? Promise.delay 10, {b: b} lifetime = {} promiseCWithCleanup = hinoki(source, lifetime, 'c') promiseA = lifetime.a promiseB = lifetime.b promiseC = lifetime.c t.ok promiseA? t.ok promiseB? t.ok promiseC? t.notEqual promiseCWithCleanup, hinoki(source, lifetime, 'c') # these are already awaiting resolution # getting them again returns the cached promises t.equal promiseA, hinoki(source, lifetime, 'a') t.equal promiseB, hinoki(source, lifetime, 'b') t.equal promiseC, hinoki(source, lifetime, 'c') Promise.all([promiseCWithCleanup, promiseA, promiseB, promiseC]) .then ([valueCWithCleanup, valueA, valueB, valueC]) -> t.equal valueA, a t.equal valueB.a, valueA t.equal valueC.b, valueB # both promises resolve to the identical value t.equal valueCWithCleanup, valueC t.equal lifetime.a, valueA t.equal lifetime.b, valueB t.equal lifetime.c, valueC t.end() test 'promise in promise', (t) -> value = {} promise = Promise.resolve value promisePromise = Promise.resolve promise source = hinoki.source a: -> promisePromise lifetime = {} hinoki source, lifetime, (a) -> t.equal a, value t.end() test 'lifetime can be omitted', (t) -> source = hinoki.source a: -> 1 b: (a) -> a + 1 c: (b) -> b + 1 hinoki source, (c) -> t.equal c, 3 t.end() test 'flat factory objects', (t) -> source = hinoki.source alpha: bravo: (bravo) -> bravo charlie: (charlie) -> charlie delta: (delta) -> delta bravo: -> 'bravo' charlie: -> 'charlie' delta: -> 'delta' lifetime = {} hinoki source, lifetime, (alpha) -> t.deepEqual alpha, bravo: 'bravo' charlie: 'charlie' delta: 'delta' t.end() test 'flat factory arrays', (t) -> source = hinoki.source alpha: [ (bravo) -> bravo (charlie) -> charlie (delta) -> delta ] bravo: -> 'bravo' charlie: -> 'charlie' delta: -> 'delta' lifetime = {} hinoki source, lifetime, (alpha) -> t.deepEqual alpha, [ 'bravo' 'charlie' 'delta' ] t.end() test 'nested factory objects and factory arrays', (t) -> source = hinoki.source alpha: bravo: charlie: delta: (delta) -> delta echo: foxtrot: (bravo) -> bravo golf: [ (delta, bravo) -> delta + '_' + bravo [ (charlie) -> charlie -> 'foxtrot' ] ] bravo: -> 'bravo' charlie: -> 'charlie' delta: -> 'delta' lifetime = {} hinoki source, lifetime, (alpha) -> t.deepEqual alpha, bravo: charlie: delta: 'delta' echo: foxtrot: 'bravo' golf: [ 'delta_bravo' [ 'charlie' 'foxtrot' ] ] t.end() test 'decorateSourceToAlsoLookupWithPrefix', (t) -> t.test 'original found', (t) -> value = {} source = hinoki.source a: -> value source = hinoki.decorateSourceToAlsoLookupWithPrefix source, 'my_' lifetime = {} hinoki source, lifetime, (a) -> t.equal a, value t.equal lifetime.a, value t.ok not lifetime.my_a? t.end() t.test 'already prefixed', (t) -> value = {} source = hinoki.source my_a: -> value source = hinoki.decorateSourceToAlsoLookupWithPrefix source, 'my_' lifetime = {} hinoki source, lifetime, (my_a) -> t.equal my_a, value t.equal lifetime.my_a, value t.ok not lifetime.a? t.end() t.test 'prefixed found', (t) -> value = {} source = hinoki.source my_a: -> value source = hinoki.decorateSourceToAlsoLookupWithPrefix source, 'my_' lifetime = {} hinoki source, lifetime, (a) -> t.equal a, value t.equal lifetime.my_a, value t.equal lifetime.a, value t.end() t.test 'not found', (t) -> source = hinoki.source {} source = hinoki.decorateSourceToAlsoLookupWithPrefix source, 'my_' lifetime = {} hinoki(source, lifetime, 'a').catch hinoki.NotFoundError, (error) -> t.equal error.message, "neither value nor factory found for `my_a` in path `my_a <- a`" t.deepEqual error.path, ['my_a', 'a'] t.deepEqual lifetime, {} t.end()