cleanup and rearrangement
[squeep-lazy-property] / test / lazy.js
index b95be17d00f6e31306228cc2459913a4dd2939bd..85ef05b98ae2e3ac3de4e7c0fdd1c4fff292b838 100644 (file)
@@ -1,4 +1,6 @@
 /* eslint-env mocha */
+/* eslint-disable capitalized-comments */
+/* eslint-disable security/detect-object-injection */
 'use strict';
 
 const assert = require('assert');
@@ -9,20 +11,20 @@ describe('lazy', function () {
 
   beforeEach(function () {
     o = {};
-    called = false;
+    called = 0;
     initializer = () => {
-      called = true;
+      called += 1;
       return 'value';
     };
   });
 
   it('does not initialize if not accessed', function () {
     lazy(o, 'p', initializer);
-    assert.strictEqual(called, false);
+    assert.strictEqual(called, 0);
     assert(Object.keys(o).includes('p'));
   });
 
-  it('wants a callable initializer', function () {
+  it('requires a callable initializer', function () {
     try {
       lazy(o, 'p', undefined);
       assert.fail('should reject un-callable initializer');
@@ -31,18 +33,20 @@ describe('lazy', function () {
     }
   });
 
-  it('initializes if accessed', function () {
+  it('initializes once when accessed', function () {
     lazy(o, 'p', initializer);
     const v = o.p;
-    assert.strictEqual(called, true);
+    const v2 = o.p;
+    assert.strictEqual(called, 1);
     assert.strictEqual(v, 'value');
+    assert.strictEqual(v2, 'value');
   });
 
   it('handles symbolic properties', function () {
     const s = Symbol('s');
     lazy(o, s, initializer);
     const v = o[s];
-    assert.strictEqual(called, true);
+    assert.strictEqual(called, 1);
     assert.strictEqual(v, 'value');
   });
 
@@ -52,7 +56,63 @@ describe('lazy', function () {
     o.p = expected;
     const v = o.p;
     assert.strictEqual(v, expected);
-    assert.strictEqual(called, false);
+    assert.strictEqual(called, 0);
   });
 
-}); // lazy
+  it('cannot be overwritten before being read if eventually not writable', function () {
+    lazy(o, 'p', initializer, {
+      writable: false,
+    });
+    try {
+      o.p = 'nope';
+      assert.fail('should disallow setting non-writable property');
+    } catch (e) {
+      assert(e instanceof TypeError, `expected 'TypeError', got ${e.name}`);
+    }
+  });
+
+  it('async initializer awkwardly needs await on all gets but technically works', async function () {
+    lazy(o, 'p', async () => {
+      called += 1;
+      return 'value';
+    });
+    const v = await o.p;
+    assert.strictEqual(called, 1);
+    assert.strictEqual(v, 'value');
+  });
+
+  it('prototypical lazy sets value on prototype by default', function () {
+    lazy(o, 'p', initializer);
+    o.id = 'proto';
+    const o1 = Object.create(o);
+    o1.id = 'o1';
+    const o2 = Object.create(o);
+    o2.id = 'o2';
+    const v1 = o1.p;
+    const v2 = o2.p;
+
+    assert.strictEqual(v1, 'value');
+    assert.strictEqual(v2, 'value');
+    assert.strictEqual(called, 1);
+    assert(!Object.hasOwnProperty.call(o1, 'p'));
+    assert(!Object.hasOwnProperty.call(o2, 'p'));
+  });
+
+  it('prototypical lazy sets value on inherited objects when requested', function () {
+    lazy(o, 'p', initializer, undefined, false);
+    o.id = 'proto';
+    const o1 = Object.create(o);
+    o1.id = 'o1';
+    const o2 = Object.create(o);
+    o2.id = 'o2';
+    const v1 = o1.p;
+    const v2 = o2.p;
+
+    assert.strictEqual(v1, 'value');
+    assert.strictEqual(v2, 'value');
+    assert.strictEqual(called, 2);
+    assert(Object.hasOwnProperty.call(o1, 'p'));
+    assert(Object.hasOwnProperty.call(o2, 'p'));
+  });
+
+}); // lazy
\ No newline at end of file