diff --git a/theme/.babelrc b/theme/.babelrc new file mode 100644 index 0000000..b1d4ead --- /dev/null +++ b/theme/.babelrc @@ -0,0 +1,12 @@ +{ + "env": { + "test": { + "plugins": [ + "transform-class-properties", + "transform-es2015-modules-commonjs", + "transform-object-rest-spread", + ["transform-react-jsx", { "pragma": "createElement" }] + ] + } + } +} diff --git a/theme/package-lock.json b/theme/package-lock.json index 1b9d954..f48f4ce 100644 --- a/theme/package-lock.json +++ b/theme/package-lock.json @@ -11232,7 +11232,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true + "dev": true, + "optional": true }, "uglifyjs-webpack-plugin": { "version": "1.1.8", diff --git a/theme/src/__tests__/utils.spec.js b/theme/src/__tests__/utils.spec.js new file mode 100644 index 0000000..3cd040a --- /dev/null +++ b/theme/src/__tests__/utils.spec.js @@ -0,0 +1,62 @@ +import { extract, fixedObserver, initObserver } from '../utils'; + +// mock a simple observer-type generator +function* arrayObserver() { + let array = []; + + array.push(yield); + array.push(yield); + + return array; +} + +// mock dynamic imports +async function dynamicImport() { + return { foo: 'foo', default: 'bar' }; +} + +test('`extract` retrieves a named export', async () => { + const foo = await extract(dynamicImport(), 'foo'); + + expect(foo).toBe('foo'); +}); + +test('`extract` retrieves the default export', async () => { + const bar = await extract(dynamicImport()); + + expect(bar).toBe('bar'); +}); + +test('`extract` throws if the module fails to resolve', async () => { + const error = new Error('Invalid namespace object provided.'); + + expect(extract(null)).rejects.toEqual(error); +}); + +test('`extract` throws if the binding is not present', async () => { + const error = new Error('Binding baz not found.'); + + expect(extract(dynamicImport(), 'baz')).rejects.toEqual(error); +}); + +test('`fixedObserver` yields undefined', () => { + const gen = fixedObserver(2); + + expect(gen.next()).toEqual({ value: void 0, done: false }); + expect(gen.next()).toEqual({ value: void 0, done: false }); + expect(gen.next()).toEqual({ value: void 0, done: true }); +}); + +test('`fixedObserver` terminates if `length` is 0', () => { + const gen = fixedObserver(0); + + expect(gen.next()).toEqual({ value: void 0, done: true }); + expect(gen.next()).toEqual({ value: void 0, done: true }); +}); + +test('`initObserver` starts an observer', () => { + const gen = initObserver(arrayObserver)(); + + expect(gen.next(0)).toEqual({ value: void 0, done: false }); + expect(gen.next(1)).toEqual({ value: [0, 1], done: true }); +}); diff --git a/theme/src/utils.js b/theme/src/utils.js index 8b3b7e6..760ce0f 100644 --- a/theme/src/utils.js +++ b/theme/src/utils.js @@ -1,15 +1,22 @@ /** - * Extract a single export from a module. + * Retrieve a single exported binding from a module. * * @param {object} obj - A module's namespace object - * @param {string} name - The key to look up + * @param {string} name - The binding to retrieve + * @returns {Promise<*>} */ export const extract = (obj, name = 'default') => - Promise.resolve(obj) - .then(mod => mod[name]) - .catch(() => { - throw new Error(`Object is not a valid module.`); - }); + Promise.resolve(obj).then(mod => { + if (!mod || typeof mod !== 'object') { + throw new Error('Invalid namespace object provided.'); + } + + if (!mod.hasOwnProperty(name)) { + throw new Error(`Binding ${name} not found.`); + } + + return mod[name]; + }); /** * Create an observer-type generator that yields a fixed number of values.