29
expect(true).to.be.true;

In this code, all the 'to', 'be', 'true' seems to be an attribute of the object response from 'expect(true)'.

How can these attributes work so that they can raise an exception?

wangzi6147
  • 293
  • 1
  • 3
  • 5
  • 1
    I think it works via javascript's [`__defineGetter__`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__defineGetter__) interface. – Juzer Ali Jun 26 '17 at 09:11

2 Answers2

27

You can check the source code:

[ 'to', 'be', 'been'
  , 'is', 'and', 'has', 'have'
  , 'with', 'that', 'which', 'at'
  , 'of', 'same', 'but', 'does' ].forEach(function (chain) {
    Assertion.addProperty(chain);
});

and there is a addProperty in utils:
https://github.com/chaijs/chai/blob/master/lib/chai/utils/addProperty.js

With this you can chain the properties infinitely like: .to.be.to.to.to.be.equal()

Let's create a simpler demonstration:

Assume that you have an assert object, with the .true() method

const assert = {
  'true': function (v) {
    return !!v
  }
}

and you want to be able to chain .to infinitely. Simply Use the defineProperty to define our getter:

Object.defineProperty(assert, 'to', {
  get() {
    return assert
  }
})

so now you can

assert.to.to.to.to.true(false)

working code: https://codepen.io/CodinCat/pen/LLzBEX?editors=0012


I've added another more complex example here: https://codepen.io/CodinCat/pen/dRVjXL?editors=0012

In this example you can see that there is some behaviors in the .true property.

We store the value from expect() in the internal __expectObj and the __value property, and then verify it in the getter of .true. So you can

expect(false).to.to.to.to.true
CodinCat
  • 15,530
  • 5
  • 49
  • 60
  • 1
    The code where `true` is a function is superfluous, I'm sure OP knows how to write a function. The actual answer to the question is hidden behind a link; please move it to your answer. – BlueRaja - Danny Pflughoeft Jun 26 '17 at 14:13
  • 1
    Yeah, the case where `.true` is a property (not a method) is what I was most curious about, as well. In short, there's a getter function for `.true` that is triggered on a look-up as in `expect(false).true`, and that getter function runs the test... Wow, interesting! – Niko Bellic Nov 10 '17 at 21:01
7

Take a look at the source of chai Assertion but the tl;dr is that Chai implements its own chainable methods on its assert library. However, special keywords are simply syntactic sugar as in the code below shows. Literally they are just properties that are added and can be chained but nothing truly is defined:

  /**
   * ### Language Chains
   *
   * The following are provided as chainable getters to improve the readability
   * of your assertions.
   *
   * **Chains**
   *
   * - to
   * - be
   * - been
   * - is
   * - that
   * - which
   * - and
   * - has
   * - have
   * - with
   * - at
   * - of
   * - same
   * - but
   * - does
   *
   * @name language chains
   * @namespace BDD
   * @api public
   */

  [ 'to', 'be', 'been'
  , 'is', 'and', 'has', 'have'
  , 'with', 'that', 'which', 'at'
  , 'of', 'same', 'but', 'does' ].forEach(function (chain) {
    Assertion.addProperty(chain);
  });

From there what it actually looks for are keywords it specifically defines. So one example is the keyword .to.be.true it will look at true as defined in the code snippet below

  /**
   * ### .true
   *
   * Asserts that the target is strictly (`===`) equal to `true`.
   *
   *     expect(true).to.be.true;
   *
   * Add `.not` earlier in the chain to negate `.true`. However, it's often best
   * to assert that the target is equal to its expected value, rather than not
   * equal to `true`.
   *
   *     expect(false).to.be.false; // Recommended
   *     expect(false).to.not.be.true; // Not recommended
   *
   *     expect(1).to.equal(1); // Recommended
   *     expect(1).to.not.be.true; // Not recommended
   *
   * A custom error message can be given as the second argument to `expect`.
   *
   *     expect(false, 'nooo why fail??').to.be.true;
   *
   * @name true
   * @namespace BDD
   * @api public
   */

  Assertion.addProperty('true', function () {
    this.assert(
        true === flag(this, 'object')
      , 'expected #{this} to be true'
      , 'expected #{this} to be false'
      , flag(this, 'negate') ? false : true
    );
  });
aug
  • 11,138
  • 9
  • 72
  • 93