Skip to content

Commit

Permalink
test: types (#210)
Browse files Browse the repository at this point in the history
  • Loading branch information
miralemd authored Dec 3, 2019
1 parent 4e8a0af commit 3a86d35
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 58 deletions.
57 changes: 57 additions & 0 deletions apis/nucleus/src/sn/__tests__/load.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { load, clearFromCache } from '../load';

describe('load', () => {
let config = {};
beforeEach(() => {
config = {
logger: {
warn: sinon.stub(),
},
env: 'env',
load: sinon.stub(),
};
});
afterEach(() => {
clearFromCache('pie');
});

it('should throw when resolving to a falsy value', async () => {
const loader = () => false;
try {
await load('pie', '1.0.0', config, loader);
expect(0).to.equal(1);
} catch (e) {
expect(e.message).to.equal("Failed to load supernova: 'pie v1.0.0'");
}
});

it('should call load() with name and version', async () => {
const loader = sinon.stub();
load('pie', '1.0.0', config, loader);
expect(loader).to.have.been.calledWithExactly({ name: 'pie', version: '1.0.0' }, 'env');
});

it('should load valid sn', async () => {
const sn = { component: {} };
const loader = () => sn;
const s = await load('pie', '1.0.0', config, loader);
expect(s).to.eql(sn);
});

it('should load only once', async () => {
const sn = { component: {} };
const loader = () => sn;
const spy = sinon.spy(loader);
load('pie', '1.0.0', config, spy);
load('pie', '1.0.0', config, spy);
load('pie', '1.0.0', config, spy);
expect(spy.callCount).to.equal(1);
});

it('should fallback to global load() when custom loader is not provided', async () => {
const sn = { component: {} };
config.load.returns(sn);
const s = await load('pie', '1.0.0', config);
expect(s).to.eql(sn);
});
});
26 changes: 17 additions & 9 deletions apis/nucleus/src/sn/__tests__/type.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,21 @@ describe('type', () => {
let SNFactory;
let load;
let satisfies;
let create;
let sb;
before(() => {
sb = sinon.createSandbox();
SNFactory = sb.stub();
load = sb.stub();
satisfies = sb.stub();
[{ default: create }] = mock({ SNFactory, load, satisfies });
});
beforeEach(() => {
SNFactory = sinon.stub();
load = sinon.stub();
satisfies = sinon.stub();
const [{ default: create }] = mock({ SNFactory, load, satisfies });
c = create({ name: 'pie', version: '1.1.0' }, 'c', { load: 'customLoader' });
});
afterEach(() => {
sb.reset();
});

describe('create', () => {
it('should instantiate a type', () => {
Expand All @@ -34,17 +42,17 @@ describe('type', () => {
});

it('should return true when no meta is provided', () => {
const cc = mock({ satisfies })[0].default({});
const cc = create({});
expect(cc.supportsPropertiesVersion('1.2.0')).to.equal(true);
});

it('should return true when no version is provided', () => {
const cc = mock({ satisfies })[0].default({}, { deps: { properties: 'a' } });
expect(cc.supportsPropertiesVersion()).to.equal(true);
const c3 = create({}, 'c', { meta: { deps: { properties: 'a' } } });
expect(c3.supportsPropertiesVersion()).to.equal(true);
});

it('should return semver satisfaction when version and semver range is provided ', () => {
const cc = mock({ satisfies })[0].default({}, 'c', { meta: { deps: { properties: '^1.0.0' } } });
const cc = create({}, 'c', { meta: { deps: { properties: '^1.0.0' } } });
expect(cc.supportsPropertiesVersion('1.2.0')).to.equal('a bool');
});
});
Expand All @@ -67,7 +75,7 @@ describe('type', () => {
const def = Promise.resolve('def');
const normalized = { qae: { properties: { initial: { a: 'a', b: 'b' } } } };

load.withArgs('pie', '1.1.0', 'c').returns(def);
load.withArgs('pie', '1.1.0', 'c', 'customLoader').returns(def);
SNFactory.withArgs('def').returns(normalized);

const props = await c.initialProperties({ c: 'c', b: 'override' });
Expand Down
86 changes: 50 additions & 36 deletions apis/nucleus/src/sn/__tests__/types.spec.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
import { semverSort } from '../types';
const mock = ({ createType, clearFromCache = () => {} } = {}) =>
aw.mock(
[
['**/sn/type.js', () => createType],
[
'**/sn/load.js',
() => ({
clearFromCache,
}),
],
],
['../types']
);

describe('types', () => {
describe('semverSort', () => {
it('should sort versions', () => {
const arr = semverSort(['1.41.0', '0.0.1', '10.4.0', '0.4.0', '1.4.0']);
expect(arr).to.eql(['0.0.1', '0.4.0', '1.4.0', '1.41.0', '10.4.0']);
});
let sb;
let create;
let semverSort;
let c;
let type;
let clearFromCache;
before(() => {
sb = sinon.createSandbox();
type = sb.stub();
clearFromCache = sb.stub();
[{ create, semverSort }] = mock({ createType: type, clearFromCache });
});

describe('factory', () => {
const mock = ({ createType = () => ({}), clearFromCache = () => {} } = {}) =>
aw.mock(
[
['**/sn/type.js', () => createType],
[
'**/sn/load.js',
() => ({
clearFromCache,
}),
],
],
['../types']
);
beforeEach(() => {
c = create({ config: 'config' });
type.returns('t');
});

let c;
let type;
beforeEach(() => {
type = sinon.stub();
type.returns({});
afterEach(() => {
sb.reset();
});

const [{ create }] = mock({
createType: type,
});
c = create({ config: 'config' });
describe('semverSort', () => {
it('should sort valid versions', () => {
const arr = semverSort(['1.41.0', '0.0.1', 'undefined', '10.4.0', '0.4.0', '1.4.0']);
expect(arr).to.eql(['undefined', '0.0.1', '0.4.0', '1.4.0', '1.41.0', '10.4.0']);
});
});

describe('factory', () => {
it('should instantiate a type when registering', () => {
c.register({ name: 'pie', version: '1.0.3' }, 'opts');
expect(type).to.have.been.calledWithExactly(
Expand All @@ -58,12 +66,14 @@ describe('types', () => {
const supportsPropertiesVersion = sinon.stub();
supportsPropertiesVersion.withArgs('1.2.0').returns(true);

type = ({ version }) => ({
supportsPropertiesVersion: version === '1.5.1' ? supportsPropertiesVersion : () => false,
type.returns({
supportsPropertiesVersion: () => false,
});
const [{ create }] = mock({
createType: type,

type.withArgs({ name: 'pie', version: '1.5.1' }).returns({
supportsPropertiesVersion,
});

c = create({ config: 'config' });

c.register({ name: 'pie', version: '1.5.0' });
Expand All @@ -81,11 +91,15 @@ describe('types', () => {
});

it('should return the requested type and version', () => {
const [{ create }] = mock({
createType: ({ name, version }) => ({ name, version }),
});
type.withArgs({ name: 'bar', version: '1.7.0' }).returns({ name: 'bar', version: '1.7.0' });
c = create({ config: 'config' });
expect(c.get({ name: 'bar', version: '1.7.0' })).to.eql({ name: 'bar', version: '1.7.0' });
});

it('should clear cache', () => {
c = create({ config: 'config' });
c.clearFromCache('pie');
expect(clearFromCache).to.have.been.calledWithExactly('pie');
});
});
});
20 changes: 8 additions & 12 deletions apis/nucleus/src/sn/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,29 @@ const LOADED = {};
* @returns {Promise<Supernova>}
*/

export function load(name, version, config, loader) {
export async function load(name, version, config, loader) {
const key = `${name}__${version}`;
if (!LOADED[key]) {
const sKey = `${name}${version && ` v${version}`}`;
const p = (loader || config.load)(
{
name,
version,
},
config.env
);
if (typeof p === 'undefined') {
throw new Error(`Failed to load supernova: ${name}`);
}
if (typeof p === 'string') {
throw new Error('Return value must be a Promise');
}
LOADED[key] = p
const prom = Promise.resolve(p);
LOADED[key] = prom
.then(sn => {
if (!sn) {
throw new Error('undefined supernova');
// TODO - improve validation
throw new Error(`load() of supernova '${sKey}' resolved to an invalid object`);
}
return sn;
})
.catch(e => {
// eslint-disable-next-line no-console
console.error(e);
throw new Error(`Failed to load supernova: ${name}`);
config.logger.warn(e);
throw new Error(`Failed to load supernova: '${sKey}'`);
});
}

Expand Down
2 changes: 1 addition & 1 deletion apis/nucleus/src/sn/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function create(info, config, opts = {}) {
const type = {
name: info.name,
version: info.version,
supportsPropertiesVersion: v => {
supportsPropertiesVersion(v) {
if (v && meta && meta.deps && meta.deps.properties) {
return satisfies(v, meta.deps.properties);
}
Expand Down
1 change: 1 addition & 0 deletions apis/nucleus/src/sn/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function typeCollection(name, config) {
if (versions[version]) {
throw new Error(`Supernova '${name}@${version}' already registered.`);
}

versions[version] = type(
{
name,
Expand Down

0 comments on commit 3a86d35

Please sign in to comment.