Skip to content

Commit

Permalink
Add data structures for chart plugin system (apache#6028)
Browse files Browse the repository at this point in the history
* add unit tests

* add test structure

* add unit tests for Registry

* add LoaderRegistry unit test

* add unit test for makeSingleton

* add type check

* add plugin data structures

* simplify API

* add preset tests

* update test message

* fix lint

* update makeSingleton

* update Plugin, Preset and unit test

* revise Registry code

* update unit test, add remove function

* update test

* update unit test

* update plugin unit test

* add .keys(), .entries() and .entriesAsPromise()

* update test description
  • Loading branch information
kristw authored and betodealmeida committed Oct 12, 2018
1 parent 54b7114 commit be5913f
Show file tree
Hide file tree
Showing 16 changed files with 602 additions and 0 deletions.
175 changes: 175 additions & 0 deletions superset/assets/spec/javascripts/modules/Registry_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import Registry from '../../../src/modules/Registry';

describe('Registry', () => {
it('exists', () => {
expect(Registry !== undefined).to.equal(true);
});

describe('new Registry(name)', () => {
it('can create a new registry when name is not given', () => {
const registry = new Registry();
expect(registry).to.be.instanceOf(Registry);
});
it('can create a new registry when name is given', () => {
const registry = new Registry('abc');
expect(registry).to.be.instanceOf(Registry);
expect(registry.name).to.equal('abc');
});
});

describe('.has(key)', () => {
it('returns true if an item with the given key exists', () => {
const registry = new Registry();
registry.registerValue('a', 'testValue');
expect(registry.has('a')).to.equal(true);
registry.registerLoader('b', () => 'testValue2');
expect(registry.has('b')).to.equal(true);
});
it('returns false if an item with the given key does not exist', () => {
const registry = new Registry();
expect(registry.has('a')).to.equal(false);
});
});

describe('.registerValue(key, value)', () => {
it('registers the given value with the given key', () => {
const registry = new Registry();
registry.registerValue('a', 'testValue');
expect(registry.has('a')).to.equal(true);
expect(registry.get('a')).to.equal('testValue');
});
it('returns the registry itself', () => {
const registry = new Registry();
expect(registry.registerValue('a', 'testValue')).to.equal(registry);
});
});

describe('.registerLoader(key, loader)', () => {
it('registers the given loader with the given key', () => {
const registry = new Registry();
registry.registerLoader('a', () => 'testValue');
expect(registry.has('a')).to.equal(true);
expect(registry.get('a')).to.equal('testValue');
});
it('returns the registry itself', () => {
const registry = new Registry();
expect(registry.registerLoader('a', () => 'testValue')).to.equal(registry);
});
});

describe('.get(key)', () => {
it('given the key, returns the value if the item is a value', () => {
const registry = new Registry();
registry.registerValue('a', 'testValue');
expect(registry.get('a')).to.equal('testValue');
});
it('given the key, returns the result of the loader function if the item is a loader', () => {
const registry = new Registry();
registry.registerLoader('b', () => 'testValue2');
expect(registry.get('b')).to.equal('testValue2');
});
it('returns null if the item with specified key does not exist', () => {
const registry = new Registry();
expect(registry.get('a')).to.equal(null);
});
it('If the key was registered multiple times, returns the most recent item.', () => {
const registry = new Registry();
registry.registerValue('a', 'testValue');
expect(registry.get('a')).to.equal('testValue');
registry.registerLoader('a', () => 'newValue');
expect(registry.get('a')).to.equal('newValue');
});
});

describe('.getAsPromise(key)', () => {
it('given the key, returns a promise of item value if the item is a value', () => {
const registry = new Registry();
registry.registerValue('a', 'testValue');
return registry.getAsPromise('a').then((value) => {
expect(value).to.equal('testValue');
});
});
it('given the key, returns a promise of result of the loader function if the item is a loader ', () => {
const registry = new Registry();
registry.registerLoader('a', () => 'testValue');
return registry.getAsPromise('a').then((value) => {
expect(value).to.equal('testValue');
});
});
it('returns a rejected promise if the item with specified key does not exist', () => {
const registry = new Registry();
return registry.getAsPromise('a').then(null, (err) => {
expect(err).to.equal('Item with key "a" is not registered.');
});
});
it('If the key was registered multiple times, returns a promise of the most recent item.', () => {
const registry = new Registry();
registry.registerValue('a', 'testValue');
const promise1 = registry.getAsPromise('a').then((value) => {
expect(value).to.equal('testValue');
});
registry.registerLoader('a', () => 'newValue');
const promise2 = registry.getAsPromise('a').then((value) => {
expect(value).to.equal('newValue');
});
return Promise.all([promise1, promise2]);
});
});

describe('.keys()', () => {
it('returns an array of keys', () => {
const registry = new Registry();
registry.registerValue('a', 'testValue');
registry.registerLoader('b', () => 'test2');
expect(registry.keys()).to.deep.equal(['a', 'b']);
});
});

describe('.entries()', () => {
it('returns an array of { key, value }', () => {
const registry = new Registry();
registry.registerValue('a', 'test1');
registry.registerLoader('b', () => 'test2');
expect(registry.entries()).to.deep.equal([
{ key: 'a', value: 'test1' },
{ key: 'b', value: 'test2' },
]);
});
});

describe('.entriesAsPromise()', () => {
it('returns a Promise of an array { key, value }', () => {
const registry = new Registry();
registry.registerValue('a', 'test1');
registry.registerLoader('b', () => 'test2');
registry.registerLoader('c', () => Promise.resolve('test3'));
return registry.entriesAsPromise().then((entries) => {
expect(entries).to.deep.equal([
{ key: 'a', value: 'test1' },
{ key: 'b', value: 'test2' },
{ key: 'c', value: 'test3' },
]);
});
});
});

describe('.remove(key)', () => {
it('removes the item with given key', () => {
const registry = new Registry();
registry.registerValue('a', 'testValue');
registry.remove('a');
expect(registry.get('a')).to.equal(null);
});
it('does not throw error if the key does not exist', () => {
const registry = new Registry();
expect(() => registry.remove('a')).to.not.throw();
});
it('returns itself', () => {
const registry = new Registry();
registry.registerValue('a', 'testValue');
expect(registry.remove('a')).to.equal(registry);
});
});
});
9 changes: 9 additions & 0 deletions superset/assets/spec/javascripts/utils/isRequired_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { it, describe } from 'mocha';
import { expect } from 'chai';
import isRequired from '../../../src/utils/isRequired';

describe('isRequired(field)', () => {
it('should throw error with the given field in the message', () => {
expect(() => isRequired('myField')).to.throw(Error, 'myField is required.');
});
});
38 changes: 38 additions & 0 deletions superset/assets/spec/javascripts/utils/makeSingleton_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import makeSingleton from '../../../src/utils/makeSingleton';

describe('makeSingleton()', () => {
class Dog {
constructor(name) {
this.name = name;
}
sit() {
this.isSitting = true;
}
}
describe('makeSingleton(BaseClass)', () => {
const getInstance = makeSingleton(Dog);

it('returns a function for getting singleton instance of a given base class', () => {
expect(getInstance).to.be.a('Function');
expect(getInstance()).to.be.instanceOf(Dog);
});
it('returned function returns same instance across all calls', () => {
expect(getInstance()).to.equal(getInstance());
});
});
describe('makeSingleton(BaseClass, ...args)', () => {
const getInstance = makeSingleton(Dog, 'Doug');

it('returns a function for getting singleton instance of a given base class constructed with the given arguments', () => {
expect(getInstance).to.be.a('Function');
expect(getInstance()).to.be.instanceOf(Dog);
expect(getInstance().name).to.equal('Doug');
});
it('returned function returns same instance across all calls', () => {
expect(getInstance()).to.equal(getInstance());
});
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import ChartPlugin from '../../../../src/visualizations/core/models/ChartPlugin';
import ChartMetadata from '../../../../src/visualizations/core/models/ChartMetadata';

describe('ChartPlugin', () => {
const metadata = new ChartMetadata({});

it('exists', () => {
expect(ChartPlugin).to.not.equal(undefined);
});

describe('new ChartPlugin()', () => {
it('creates a new plugin', () => {
const plugin = new ChartPlugin({
metadata,
Chart() {},
});
expect(plugin).to.be.instanceof(ChartPlugin);
});
it('throws an error if metadata is not specified', () => {
expect(() => new ChartPlugin()).to.throw(Error);
});
it('throws an error if none of Chart or loadChart is specified', () => {
expect(() => new ChartPlugin({ metadata })).to.throw(Error);
});
});

describe('.register(key)', () => {
const plugin = new ChartPlugin({
metadata,
Chart() {},
});
it('throws an error if key is not provided', () => {
expect(() => plugin.register()).to.throw(Error);
expect(() => plugin.configure({ key: 'abc' }).register()).to.not.throw(Error);
});
it('returns itself', () => {
expect(plugin.configure({ key: 'abc' }).register()).to.equal(plugin);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import Plugin from '../../../../src/visualizations/core/models/Plugin';

describe('Plugin', () => {
it('exists', () => {
expect(Plugin).to.not.equal(undefined);
});

describe('new Plugin()', () => {
it('creates a new plugin', () => {
const plugin = new Plugin();
expect(plugin).to.be.instanceof(Plugin);
});
});

describe('.configure(config, replace)', () => {
it('extends the default config with given config when replace is not set or false', () => {
const plugin = new Plugin();
plugin.configure({ key: 'abc', foo: 'bar' });
plugin.configure({ key: 'def' });
expect(plugin.config).to.deep.equal({ key: 'def', foo: 'bar' });
});
it('replaces the default config with given config when replace is true', () => {
const plugin = new Plugin();
plugin.configure({ key: 'abc', foo: 'bar' });
plugin.configure({ key: 'def' }, true);
expect(plugin.config).to.deep.equal({ key: 'def' });
});
it('returns the plugin itself', () => {
const plugin = new Plugin();
expect(plugin.configure({ key: 'abc' })).to.equal(plugin);
});
});

describe('.resetConfig()', () => {
it('resets config back to default', () => {
const plugin = new Plugin();
plugin.configure({ key: 'abc', foo: 'bar' });
plugin.resetConfig();
expect(plugin.config).to.deep.equal({});
});
it('returns the plugin itself', () => {
const plugin = new Plugin();
expect(plugin.resetConfig()).to.equal(plugin);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import Preset from '../../../../src/visualizations/core/models/Preset';
import Plugin from '../../../../src/visualizations/core/models/Plugin';

describe('Preset', () => {
it('exists', () => {
expect(Preset).to.not.equal(undefined);
});

describe('new Preset()', () => {
it('creates new preset', () => {
const preset = new Preset();
expect(preset).to.be.instanceOf(Preset);
});
});

describe('.register()', () => {
it('register all listed presets then plugins', () => {
const values = [];
class Plugin1 extends Plugin {
register() {
values.push(1);
}
}
class Plugin2 extends Plugin {
register() {
values.push(2);
}
}
class Plugin3 extends Plugin {
register() {
values.push(3);
}
}
class Plugin4 extends Plugin {
register() {
const { key } = this.config;
values.push(key);
}
}

const preset1 = new Preset({
plugins: [new Plugin1()],
});
const preset2 = new Preset({
plugins: [new Plugin2()],
});
const preset3 = new Preset({
presets: [preset1, preset2],
plugins: [
new Plugin3(),
new Plugin4().configure({ key: 'abc' }),
],
});
preset3.register();
expect(values).to.deep.equal([1, 2, 3, 'abc']);
});

it('returns itself', () => {
const preset = new Preset();
expect(preset.register()).to.equal(preset);
});
});
});
Loading

0 comments on commit be5913f

Please sign in to comment.