Skip to content

Commit

Permalink
0.2.0: More tests, improved README
Browse files Browse the repository at this point in the history
  • Loading branch information
Download committed Apr 11, 2017
1 parent 0742a51 commit d5495a0
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 63 deletions.
114 changes: 67 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ is(animal).a(Lion)
```

Often, we don't really care whether the object *is* a certain type, we just want to know whether
we can treat is *as* a certain type. Use `is(subject).as(type)` to test whether a subject adheres
we can treat it *as* a certain type. Use `is(subject).as(type)` to test whether a subject adheres
to the same interface as is defined by `type`:

```js
Expand All @@ -111,53 +111,39 @@ is(viewer).a(Looker) // false, but
is(viewer).as(Looker) // true
```

Now let us make some more mixins:
A good example of how this might be useful can be found in the new ES6 feature Promises. Here we have
the concept of a 'thenable'. This is any object that has a `then` method on it. Methods in the Promise
API often accept thenables instead of promise instances. Have a look at `Promise.resolve` for example:

```js
var Walker = mix(superclass => class Walker extends superclass {
constructor() {
super()
console.info('A walker is born!')
}
walk() {
console.info('Step step step...')
}
})
> Promise.resolve(value)
> Returns a Promise object that is resolved with the given value. If the value is a thenable (i.e.
> has a then method), the returned promise will "follow" that thenable, adopting its eventual state;
> otherwise the returned promise will be fulfilled with the value.
<sup>[mdn](https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Global_Objects/Promise)</sup>
var walker = new Walker() // > A walker is born!
walker.walk() // > Step step step...
Using `mix` to define an interface and `is..as` to test for it, we can very naturally express the
concept of a thenable from the Promise spec in code:

var Talker = mix(superclass => class Talker extends superclass {
constructor() {
super()
console.info('A talker is born!')
}
talk() {
console.info('Blah blah blah...')
}
```js
/** Defines a Thenable */
var Thenable = mix(superclass => class Thenable extends superclass {
then() {}
})

var talker = new Talker() // > A talker is born!
talker.talk() // > Blah blah blah...

var Duck = mix(Looker, Walker, Talker, superclass => class Duck extends superclass {
constructor() {
super()
console.info('A duck is born!')
}
talk() {
console.info('Quack quack...')
// optional: super.talk && super.talk()
/** Some mixin which can be treated as a Thenable */
var MyPromise = mix(superclass => class MyPromise extends superclass {
then(resolve, reject) {
resolve('Hello, World!')
}
}
// We can check whether the class is thenable using is..as
is(MyPromise).as(Thenable) // true
// we can also check instances
var promise = new MyPromise()
is(promise).as(Thenable) // true
// Ok, that means we can use Promise.resolve!
Promise.resolve(promise).then((result) => {
console.info(result) // > 'Hello, World!'
})

var duck = new Duck() // > A looker is born!
// > A walker is born!
// > A talker is born!
// > A duck is born!
talker.talk() // > Quack quack...
is(talker).a.(Duck) // true
is(talker).a(Walker) // true
```
Notice how we are only creating and using mixins up until now. Mixins are more
Expand All @@ -170,16 +156,50 @@ return a new class that derives from the superclass adding all the mixins that w
The returned class can be extended from to inherit all the mixins:
```js
class Duckish extends mix(Looker, Walker, Talker) {
talk() {
console.info('I look, walk and talk like a duck!')
class GreatLookingPromise extends mix(Looker, MyPromise) {}
var promise =
new GreatLookingPromise() // > A looker is born!
promise.look() // > Looking good!
Promise.resolve(promise).then(
r => console.info(r) // > Hello, World!
)
```
When your class needs to extend from a base class, pass it
as the first argument to `mix`:
```js
class Playboy extends mix(Looker, Talker) {
talk(){
console.info('How are *you* doin\'?')
}
}
class PlayboyPromise extends mix(Playboy, MyPromise) {}
var promise =
new PlayboyPromise() // > A talker is born!
// > A looker is born!
promise.look() // > Looking good!
promise.talk() // > How are *you* doin'?
Promise.resolve(promise).then(
r => console.info(r) // > Hello, World!
)
```
> Pro Tip: The constructors returned from `mix` Include a `with` method, that allows
> the `PlayboyPromise` to be declared even simpler:
var duckish = new Duckish()
is(duckish).as(Duck) // true
```js
class PlayboyPromise extends Playboy.with(MyPromise) {
// ...
}
```
> Pro Tip 2: Cool as `with` is, it will give you a class, which very often means you won't
> be able to mix it further. I recommend sticking to mixins whenever possible as they hardly
> have any downsides compared with regular classes. If you ever need the class, just use
> `SomeMixin.class`, that's the real ES6 class that is used to create instances of the mixin.
### Bonus
As a bonus, you can use `is(..).a(..)` to do some simple type tests by passing a
string for the type:
Expand Down
70 changes: 57 additions & 13 deletions dist/mics.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6551,7 +6551,7 @@ describe('is(x [, type])', function () {
(0, _chai.expect)((0, _.is)(x).a(Date)).to.eq((0, _.is)(x, Date));
});

it('when passed a single argument, returns an object', function () {
it('when passed a single argument, returns an object with sub-methods', function () {
(0, _chai.expect)((0, _.is)({})).to.be.an('object');
});

Expand Down Expand Up @@ -6941,6 +6941,50 @@ describe('is(x [, type])', function () {
(0, _chai.expect)((0, _.is)(Derived).as(Looker)).to.eq(true);
(0, _chai.expect)((0, _.is)(new Derived()).as(Looker)).to.eq(true);
});

it('allows mixins to be used as interfaces', function (done) {
var expected = 'Hello, World!';
var Thenable = (0, _.mix)(function (superclass) {
return function (_superclass21) {
_inherits(Thenable, _superclass21);

function Thenable() {
_classCallCheck(this, Thenable);

return _possibleConstructorReturn(this, (Thenable.__proto__ || Object.getPrototypeOf(Thenable)).apply(this, arguments));
}

_createClass(Thenable, [{
key: 'then',
value: function then(results) {}
}]);

return Thenable;
}(superclass);
});

var MyPromise = function () {
function MyPromise() {
_classCallCheck(this, MyPromise);
}

_createClass(MyPromise, [{
key: 'then',
value: function then(resolve, reject) {
resolve(expected);
}
}]);

return MyPromise;
}();

var promise = new MyPromise();
(0, _chai.expect)((0, _.is)(promise).as(Thenable)).to.eq(true);
Promise.resolve(promise).then(function (result) {
(0, _chai.expect)(result).to.eq(expected);
done();
});
});
});
});

Expand All @@ -6950,17 +6994,17 @@ describe('mix example', function () {
_look = (0, _sinon.spy)();

var Looker = (0, _.mix)(function (superclass) {
return function (_superclass21) {
_inherits(Looker, _superclass21);
return function (_superclass22) {
_inherits(Looker, _superclass22);

function Looker() {
_classCallCheck(this, Looker);

var _this28 = _possibleConstructorReturn(this, (Looker.__proto__ || Object.getPrototypeOf(Looker)).call(this));
var _this29 = _possibleConstructorReturn(this, (Looker.__proto__ || Object.getPrototypeOf(Looker)).call(this));

log.log('A looker is born!');
constr();
return _this28;
return _this29;
}

_createClass(Looker, [{
Expand Down Expand Up @@ -6995,8 +7039,8 @@ describe('mix example', function () {
_walk = (0, _sinon.spy)(),
_talk = (0, _sinon.spy)();
var Looker = (0, _.mix)(function (superclass) {
return function (_superclass22) {
_inherits(Looker, _superclass22);
return function (_superclass23) {
_inherits(Looker, _superclass23);

function Looker() {
_classCallCheck(this, Looker);
Expand All @@ -7017,8 +7061,8 @@ describe('mix example', function () {
});

var Walker = (0, _.mix)(function (superclass) {
return function (_superclass23) {
_inherits(Walker, _superclass23);
return function (_superclass24) {
_inherits(Walker, _superclass24);

function Walker() {
_classCallCheck(this, Walker);
Expand All @@ -7039,8 +7083,8 @@ describe('mix example', function () {
});

var Talker = (0, _.mix)(function (superclass) {
return function (_superclass24) {
_inherits(Talker, _superclass24);
return function (_superclass25) {
_inherits(Talker, _superclass25);

function Talker() {
_classCallCheck(this, Talker);
Expand All @@ -7062,8 +7106,8 @@ describe('mix example', function () {

var duckTalk = (0, _sinon.spy)();
var Duck = (0, _.mix)(Looker, Walker, Talker, function (superclass) {
return function (_superclass25) {
_inherits(Duck, _superclass25);
return function (_superclass26) {
_inherits(Duck, _superclass26);

function Duck() {
_classCallCheck(this, Duck);
Expand Down
2 changes: 1 addition & 1 deletion dist/mics.spec.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mics",
"version": "0.1.1",
"version": "0.2.0",
"description": "Multiple Inheritance Class System: Intuitive mixins for ES6 classes",
"main": "dist/mics.cjs.js",
"scripts": {
Expand Down
20 changes: 19 additions & 1 deletion src/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('is(x [, type])', function(){
expect(is(x).a(Date)).to.eq(is(x, Date))
})

it('when passed a single argument, returns an object', function(){
it('when passed a single argument, returns an object with sub-methods', function(){
expect(is({})).to.be.an('object')
})

Expand Down Expand Up @@ -212,6 +212,24 @@ describe('is(x [, type])', function(){
expect(is(Derived).as(Looker)).to.eq(true)
expect(is(new Derived()).as(Looker)).to.eq(true)
})

it('allows mixins to be used as interfaces', (done) => {
var expected = 'Hello, World!'
var Thenable = mix(superclass => class Thenable extends superclass {
then(results) {}
})
class MyPromise {
then(resolve, reject) {
resolve(expected)
}
}
var promise = new MyPromise()
expect(is(promise).as(Thenable)).to.eq(true)
Promise.resolve(promise).then((result) => {
expect(result).to.eq(expected)
done()
})
})
})
})

Expand Down

0 comments on commit d5495a0

Please sign in to comment.