JSlang.dev

JavaScript Deep Dives with Wolfram. Since 2015
Inclusive, Test-Driven, Spec-Focused, Collaborative.

October 22, 2020
TestsSource
Optional ?. chaining (this time for real)

Source Code

The source code we wrote during the event.

import assert from 'assert';

describe('Optional chaining (`?.` operator)', () => {
  describe('Whats the default return value if an element is undefined/null? (without the operator)', () => {
    it('applying the operator ?. on undefined returns undefined', () => {
      assert.strictEqual(undefined?.foo, undefined);
    });
    it('applying the operator on null returns undefined', () => {
      assert.strictEqual(null?.foo, undefined);
    });
    it('calling `undefined?.foo()` returns undefined', () => {
      assert.strictEqual(undefined?.foo(), undefined);
    });
    it('an expression in a fn parameter is NOT evaluated before option chaining is applied', () => {
      let i = 0;
      undefined?.foo(i++);
      assert.strictEqual(i, 0);
    });
    it('the expression `++i` as a fn parameter is NOT evaluated before optional chaining is applied', () => {
      let i = 0;
      undefined?.foo(++i);
      assert.strictEqual(i, 0);
    });
    it('the expression `++i` is not evaluated before optional chaining', () => {
      let i = 0;
      undefined?.[++i];
      assert.strictEqual(i, 0);
    });
    it('does NOT throw an error as an dynamic accessor is NOT evaluated', () => {
      assert.doesNotThrow(() => {
        undefined?.[(() => { throw Error(); })()];
      });
    });
  });

  describe('What about newlines, spaces?', () => {
    it('newline before `?.` works', () => {
      const x = {foo: 1};
      assert.doesNotThrow(() => eval(`
        x
          ?.foo;
      `));
      assert.strictEqual(x
        ?.foo, 1
      )
    });
    it('newline after `?.` works', () => {
      const x = {foo: 1};
      assert.doesNotThrow(() => eval(`
        x?.
          foo;
      `));
    });
    it('when we split the operator with a newline, then it is invalid syntax', () => {
      const x = {foo: 1};
      assert.throws(() => eval(`
        x?
          .foo
      `), SyntaxError);
    });
    it('when we split the operator with a space, then it is not valid syntax', () => {
      assert.throws(() => eval(`x? .foo`), SyntaxError);
    });
  });

  describe('How does it work with (async, generators) functions?', () => {
    it('use async fn as accessor, which returns undefined, will result in undefined', async () => {
      const fn = async () => 'a';
      const x = {};
      assert.strictEqual(x?.[await fn()], undefined);
    });
    it('async fn call on the left hand side can be optional chained', async () => {
      const fn = async () => ({foo: 42});
      assert.strictEqual(await fn()?.foo, undefined);
      assert.strictEqual((await fn())?.foo, 42);

      const fn1 = () => ({foo: Promise.resolve(42)});
      assert.strictEqual(await fn1()?.foo, 42);
    });
  });
});