Skip to content

Commit

Permalink
Added kontainer namespace and initial implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
benquarmby committed Jan 28, 2015
1 parent 416285a commit f685daa
Show file tree
Hide file tree
Showing 3 changed files with 281 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/
153 changes: 153 additions & 0 deletions source/kontainer-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
describe('kontainer', function () {
'use strict';

describe('registerFactory', function () {
it('should throw given non string name', function () {
expect(function () {
kontainer.registerFactory(true);
}).toThrowError(/name/i);
});

it('should throw given non array factory', function () {
expect(function () {
kontainer.registerFactory('name', {});
}).toThrowError(/array/i);
});

it('should accept array factory', function () {
kontainer.registerFactory('name', []);
});
});

describe('registerValue', function () {
it('should throw given non string name', function () {
expect(function () {
kontainer.registerValue(true);
}).toThrowError(/name/i);
});

it('should accept object value', function () {
kontainer.registerValue('name', {});
});

it('should accept primitive value', function () {
kontainer.registerValue('name', 'value');
});
});

describe('register', function () {
it('should throw given non string name', function () {
expect(function () {
kontainer.register(false);
}).toThrowError(/name/i);
});

it('should accept any value', function () {
kontainer.register('name', /regexp/);
kontainer.register('name', []);
kontainer.register('name', 12345);
});
});

describe('loader', function () {
var element;

ko.components.loaders.unshift(kontainer.loader);

function registerComponent(factory) {
ko.components.register('fake-component', {
viewModel: factory,
template: '<p data-bind="text: name"></p>'
});
}

beforeEach(function () {
jasmine.clock().install();
element = document.createElement('div');
element.setAttribute('data-bind', 'component: \'fake-component\'');
document.body.appendChild(element);
});

afterEach(function () {
jasmine.clock().uninstall();
ko.components.unregister('fake-component');
document.body.removeChild(element);
element = null;
});

it('should inject value into template', function () {
kontainer.registerValue('name', 'injected value');

registerComponent(['name', function (name) {
return {
name: ko.observable(name)
};
}]);

ko.applyBindings(null, element);
jasmine.clock().tick(1);
expect(element.firstChild.innerHTML).toBe('injected value');
});

it('should only invoke factories once', function () {
var factory1 = jasmine.createSpy(),
factory2 = jasmine.createSpy();

kontainer.register('service1', [factory1]);
kontainer.register('service2', ['service1', factory2]);

registerComponent(['service2', 'service1', function () {
return {
name: ko.observable()
};
}]);

ko.applyBindings(null, element);
jasmine.clock().tick(1);
expect(factory1.calls.count()).toBe(1);
expect(factory2.calls.count()).toBe(1);
});

it('should inject value from service into template', function () {
kontainer.register('name', 'injected value');
kontainer.register('service', ['name', function (name) {
var reverseName = name.split('').reverse().join('');

return {
reverseName: reverseName
};
}]);

registerComponent(['service', function (service) {
return {
name: ko.observable(service.reverseName)
};
}]);

ko.applyBindings(null, element);
jasmine.clock().tick(1);
expect(element.firstChild.innerHTML).toBe('eulav detcejni');
});

it('should throw given cyclic dependency', function () {
kontainer.registerFactory('service1', ['service2', function (name) {
return;
}]);
kontainer.registerFactory('service2', ['service1', function (name) {
return;
}]);

registerComponent(['service1', function (service) {
return {
name: ko.observable(service.reverseName)
};
}]);

ko.applyBindings(null, element);

expect(function () {
jasmine.clock().tick(1);
}).toThrowError(/fake-component > service1 > service2 > service1/i);
});
});
});
127 changes: 127 additions & 0 deletions source/kontainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
var kontainer;

kontainer = (function () {
'use strict';

var resolve,
parse,
registry = {},
states = {
unparsed: 0,
parsing: -1,
parsed: 1
},
slice = Array.prototype.slice;

resolve = function (name, path) {
if (!registry.hasOwnProperty(name)) {
throw new Error('Unknown dependency "' + name + '".');
}

path.push(name);

var dependency = registry[name];

if (dependency.state === states.parsing) {
throw new Error('Cyclic dependency detected while resolving "' + name + '". ' + path.join(' > '));
}

if (dependency.state === states.unparsed) {
dependency.state = states.parsing;
dependency.value = parse(dependency.factory, path);
delete dependency.factory;
dependency.state = states.parsed;
}

path.pop();

return dependency.value;
};

parse = function (definition, path, custom) {
var fn = definition.pop(),
args = [];

if (typeof fn !== 'function') {
throw new Error('The last element in a dependency array must be a function.');
}

custom = custom || {};

args = definition.map(function (key) {
if (typeof key !== 'string') {
throw new Error('Each element before a factory function must be a string.');
}

return custom.hasOwnProperty(key) ? custom[key] : resolve(key, path);
});

return fn.apply(undefined, args);
};

function registerFactory(name, factory) {
if (typeof name !== 'string') {
throw new Error('The name parameter must be a string.');
}

if (!Array.isArray(factory)) {
throw new Error('Factories must always be arrays.');
}

registry[name] = {
state: states.unparsed,
factory: slice.call(factory)
};
}

function registerValue(name, value) {
if (typeof name !== 'string') {
throw new Error('The name parameter must be a string.');
}

registry[name] = {
state: states.parsed,
value: value
};
}

function register(name, value) {
if (Array.isArray(value)) {
registerFactory(name, value);

return;
}

registerValue(name, value);
}

function createViewModel(name, params, componentInfo) {
return parse(
this,
[name],
{
params: params,
componentInfo: componentInfo
}
);
}

function loadViewModel(componentName, viewModelConfig, callback) {
if (Array.isArray(viewModelConfig)) {
callback(createViewModel.bind(viewModelConfig, componentName));

return;
}

callback(null);
}

return {
registerFactory: registerFactory,
registerValue: registerValue,
register: register,
loader: {
loadViewModel: loadViewModel
}
};
}());

0 comments on commit f685daa

Please sign in to comment.