I prefer @tonethar's approach, with a little bit of enhancements and digging for the benefit of understanding better the underlyings of the ES6/Node.js ecosystem. With a background in the server side of the fence, I prefer the approach of functional style around platform's primitives, this minimizes the code bloat, the slippery slope into the state's management valley of the shadow of death due to the introduction of new types and increases the readability - makes more clear the intent of the solution and the algorithm.
Solution with TDD, ES6, Node.js, Lodash, Jest, Babel, ESLint
// ./utils.js
import _ from 'lodash';
const enumOf = (...args) =>
Object.freeze( Array.from( Object.assign(args) )
.filter( (item) => _.isString(item))
.map((item) => Object.freeze(Symbol.for(item))));
const sum = (a, b) => a + b;
export {enumOf, sum};
// ./utils.js
// ./kittens.js
import {enumOf} from "./utils";
const kittens = (()=> {
const Kittens = enumOf(null, undefined, 'max', 'joe', 13, -13, 'tabby', new
Date(), 'tom');
return () => Kittens;
})();
export default kittens();
// ./kittens.js
// ./utils.test.js
import _ from 'lodash';
import kittens from './kittens';
test('enum works as expected', () => {
kittens.forEach((kitten) => {
// in a typed world, do your type checks...
expect(_.isSymbol(kitten));
// no extraction of the wrapped string here ...
// toString is bound to the receiver's type
expect(kitten.toString().startsWith('Symbol(')).not.toBe(false);
expect(String(kitten).startsWith('Symbol(')).not.toBe(false);
expect(_.isFunction(Object.valueOf(kitten))).not.toBe(false);
const petGift = 0 === Math.random() % 2 ? kitten.description :
Symbol.keyFor(kitten);
expect(petGift.startsWith('Symbol(')).not.toBe(true);
console.log(`Unwrapped Christmas kitten pet gift '${petGift}', yeee :)
!!!`);
expect(()=> {kitten.description = 'fff';}).toThrow();
});
});
// ./utils.test.js