Example #1
0
    ['@test list of properties updates when an additional property is added (such cache busting)'](
      assert
    ) {
      let MyClass = EmberObject.extend({
        foo: computed(K),

        fooDidChange: observer('foo', function() {}),

        bar: computed(K),
      });

      let list = [];

      MyClass.eachComputedProperty(function(name) {
        list.push(name);
      });

      assert.deepEqual(list.sort(), ['bar', 'foo'].sort(), 'expected two computed properties');

      MyClass.reopen({
        baz: computed(K),
      });

      MyClass.create(); // force apply mixins

      list = [];

      MyClass.eachComputedProperty(function(name) {
        list.push(name);
      });

      assert.deepEqual(
        list.sort(),
        ['bar', 'foo', 'baz'].sort(),
        'expected three computed properties'
      );

      defineProperty(MyClass.prototype, 'qux', computed(K));

      list = [];

      MyClass.eachComputedProperty(function(name) {
        list.push(name);
      });

      assert.deepEqual(
        list.sort(),
        ['bar', 'foo', 'baz', 'qux'].sort(),
        'expected four computed properties'
      );
    }
    ['@test calling metaForProperty on a native class works'](assert) {
      assert.expect(0);

      class SubEmberObject extends EmberObject {}

      defineProperty(
        SubEmberObject.prototype,
        'foo',
        computed('foo', {
          get() {
            return 'bar';
          },
        })
      );

      // able to get meta without throwing an error
      SubEmberObject.metaForProperty('foo');
    }
Example #3
0
    removeObserver(this, contentKey, null, contentPropertyDidChange);
  },

  unknownProperty(key) {
    let content = contentFor(this);
    if (content) {
      return get(content, key);
    }
  },

  setUnknownProperty(key, value) {
    let m = meta(this);

    if (m.proto === this) {
      // if marked as prototype then just defineProperty
      // rather than delegate
      defineProperty(this, key, null, value);
      return value;
    }

    let content = contentFor(this, m);

    assert(
      `Cannot delegate set('${key}', ${value}) to the \'content\' property of object proxy ${this}: its 'content' is undefined.`,
      content
    );

    return set(content, key, value);
  },
});
    '@test super and _super interop between old and new methods'(assert) {
      let calls = [];
      let changes = [];
      let events = [];
      let lastProps;

      class A extends EmberObject {
        init(props) {
          calls.push('A init');
          lastProps = props;
        }
      }

      let Mixin1 = Mixin.create({
        init() {
          calls.push('Mixin1 init before _super');
          this._super(...arguments);
          calls.push('Mixin1 init after _super');
        },
      });

      let Mixin2 = Mixin.create({
        init() {
          calls.push('Mixin2 init before _super');
          this._super(...arguments);
          calls.push('Mixin2 init after _super');
        },
      });

      class B extends A.extend(Mixin1, Mixin2) {
        init() {
          calls.push('B init before super.init');
          super.init(...arguments);
          calls.push('B init after super.init');
        }

        onSomeEvent(evt) {
          events.push(`B onSomeEvent ${evt}`);
        }

        fullNameDidChange() {
          changes.push('B fullNameDidChange');
        }
      }

      // // define a CP
      defineProperty(
        B.prototype,
        'full',
        computed('first', 'last', {
          get() {
            return this.first + ' ' + this.last;
          },
        })
      );

      // Only string observers are allowed for prototypes
      addObserver(B.prototype, 'full', null, 'fullNameDidChange');

      // Only string listeners are allowed for prototypes
      addListener(B.prototype, 'someEvent', null, 'onSomeEvent');

      B.reopen({
        init() {
          calls.push('reopen init before _super');
          this._super(...arguments);
          calls.push('reopen init after _super');
        },
      });

      let C = B.extend({
        init() {
          calls.push('C init before _super');
          this._super(...arguments);
          calls.push('C init after _super');
        },

        onSomeEvent(evt) {
          calls.push('C onSomeEvent before _super');
          this._super(evt);
          calls.push('C onSomeEvent after _super');
        },

        fullNameDidChange() {
          calls.push('C fullNameDidChange before _super');
          this._super();
          calls.push('C fullNameDidChange after _super');
        },
      });

      class D extends C {
        init() {
          calls.push('D init before super.init');
          super.init(...arguments);
          calls.push('D init after super.init');
        }

        onSomeEvent(evt) {
          events.push('D onSomeEvent before super.onSomeEvent');
          super.onSomeEvent(evt);
          events.push('D onSomeEvent after super.onSomeEvent');
        }

        fullNameDidChange() {
          changes.push('D fullNameDidChange before super.fullNameDidChange');
          super.fullNameDidChange();
          changes.push('D fullNameDidChange after super.fullNameDidChange');
        }

        triggerSomeEvent(...args) {
          sendEvent(this, 'someEvent', args);
        }
      }

      assert.deepEqual(calls, [], 'nothing has been called');
      assert.deepEqual(changes, [], 'full has not changed');
      assert.deepEqual(events, [], 'onSomeEvent has not been triggered');

      let d = D.create({ first: 'Robert', last: 'Jackson' });

      assert.deepEqual(calls, [
        'D init before super.init',
        'C init before _super',
        'reopen init before _super',
        'B init before super.init',
        'Mixin2 init before _super',
        'Mixin1 init before _super',
        'A init',
        'Mixin1 init after _super',
        'Mixin2 init after _super',
        'B init after super.init',
        'reopen init after _super',
        'C init after _super',
        'D init after super.init',
      ]);
      assert.deepEqual(changes, [], 'full has not changed');
      assert.deepEqual(events, [], 'onSomeEvent has not been triggered');

      assert.deepEqual(lastProps, {
        first: 'Robert',
        last: 'Jackson',
      });

      assert.equal(d.full, 'Robert Jackson');

      d.setProperties({ first: 'Kris', last: 'Selden' });

      // TODO: Generator transpilation code doesn't play nice with class definitions/hoisting
      return runLoopSettled().then(() => {
        assert.deepEqual(changes, [
          'D fullNameDidChange before super.fullNameDidChange',
          'B fullNameDidChange',
          'D fullNameDidChange after super.fullNameDidChange',
        ]);

        assert.equal(d.full, 'Kris Selden');

        d.triggerSomeEvent('event arg');
        assert.deepEqual(events, [
          'D onSomeEvent before super.onSomeEvent',
          'B onSomeEvent event arg',
          'D onSomeEvent after super.onSomeEvent',
        ]);
      });
    }
Example #5
0
  constructor(properties) {
    // pluck off factory
    let initFactory = factoryMap.get(this.constructor);
    if (initFactory !== undefined) {
      factoryMap.delete(this.constructor);
      FACTORY_FOR.set(this, initFactory);
    }

    // prepare prototype...
    this.constructor.proto();

    let self = this;

    let beforeInitCalled; // only used in debug builds to enable the proxy trap

    // using DEBUG here to avoid the extraneous variable when not needed
    if (DEBUG) {
      beforeInitCalled = true;
    }

    if (DEBUG && HAS_NATIVE_PROXY && typeof self.unknownProperty === 'function') {
      let messageFor = (obj, property) => {
        return (
          `You attempted to access the \`${String(property)}\` property (of ${obj}).\n` +
          `Since Ember 3.1, this is usually fine as you no longer need to use \`.get()\`\n` +
          `to access computed properties. However, in this case, the object in question\n` +
          `is a special kind of Ember object (a proxy). Therefore, it is still necessary\n` +
          `to use \`.get('${String(property)}')\` in this case.\n\n` +
          `If you encountered this error because of third-party code that you don't control,\n` +
          `there is more information at https://github.com/emberjs/ember.js/issues/16148, and\n` +
          `you can help us improve this error message by telling us more about what happened in\n` +
          `this situation.`
        );
      };

      /* globals Proxy Reflect */
      self = new Proxy(this, {
        get(target, property, receiver) {
          if (property === PROXY_CONTENT) {
            return target;
          } else if (
            beforeInitCalled ||
            typeof property === 'symbol' ||
            isInternalSymbol(property) ||
            property === 'toJSON' ||
            property === 'toString' ||
            property === 'toStringExtension' ||
            property === 'didDefineProperty' ||
            property === 'willWatchProperty' ||
            property === 'didUnwatchProperty' ||
            property === 'didAddListener' ||
            property === 'didRemoveListener' ||
            property === 'isDescriptor' ||
            property === '_onLookup' ||
            property in target
          ) {
            return Reflect.get(target, property, receiver);
          }

          let value = target.unknownProperty.call(receiver, property);

          if (typeof value !== 'function') {
            assert(messageFor(receiver, property), value === undefined || value === null);
          }
        },
      });

      FACTORY_FOR.set(self, initFactory);
    }

    let m = meta(self);
    let proto = m.proto;
    m.proto = self;

    if (properties !== undefined) {
      assert(
        'EmberObject.create only accepts objects.',
        typeof properties === 'object' && properties !== null
      );

      assert(
        'EmberObject.create no longer supports mixing in other ' +
          'definitions, use .extend & .create separately instead.',
        !(properties instanceof Mixin)
      );

      let concatenatedProperties = self.concatenatedProperties;
      let mergedProperties = self.mergedProperties;
      let hasConcatenatedProps =
        concatenatedProperties !== undefined && concatenatedProperties.length > 0;
      let hasMergedProps = mergedProperties !== undefined && mergedProperties.length > 0;

      let keyNames = Object.keys(properties);

      for (let i = 0; i < keyNames.length; i++) {
        let keyName = keyNames[i];
        let value = properties[keyName];

        if (BINDING_SUPPORT && ENV._ENABLE_BINDING_SUPPORT && Mixin.detectBinding(keyName)) {
          m.writeBindings(keyName, value);
        }

        assert(
          'EmberObject.create no longer supports defining computed ' +
            'properties. Define computed properties using extend() or reopen() ' +
            'before calling create().',
          !(value instanceof ComputedProperty)
        );
        assert(
          'EmberObject.create no longer supports defining methods that call _super.',
          !(typeof value === 'function' && value.toString().indexOf('._super') !== -1)
        );
        assert(
          '`actions` must be provided at extend time, not at create time, ' +
            'when Ember.ActionHandler is used (i.e. views, controllers & routes).',
          !(keyName === 'actions' && ActionHandler.detect(this))
        );

        let possibleDesc = descriptorFor(self, keyName, m);
        let isDescriptor = possibleDesc !== undefined;

        if (!isDescriptor) {
          let baseValue = self[keyName];

          if (hasConcatenatedProps && concatenatedProperties.indexOf(keyName) > -1) {
            if (baseValue) {
              value = makeArray(baseValue).concat(value);
            } else {
              value = makeArray(value);
            }
          }

          if (hasMergedProps && mergedProperties.indexOf(keyName) > -1) {
            value = assign({}, baseValue, value);
          }
        }

        if (isDescriptor) {
          possibleDesc.set(self, keyName, value);
        } else if (typeof self.setUnknownProperty === 'function' && !(keyName in self)) {
          self.setUnknownProperty(keyName, value);
        } else {
          if (DEBUG) {
            defineProperty(self, keyName, null, value, m); // setup mandatory setter
          } else {
            self[keyName] = value;
          }
        }
      }
    }

    if (BINDING_SUPPORT && ENV._ENABLE_BINDING_SUPPORT) {
      Mixin.finishPartial(self, m);
    }

    // using DEBUG here to avoid the extraneous variable when not needed
    if (DEBUG) {
      beforeInitCalled = false;
    }
    self.init(...arguments);

    m.proto = proto;
    finishChains(m);
    sendEvent(self, 'init', undefined, undefined, undefined, m);

    // only return when in debug builds and `self` is the proxy created above
    if (DEBUG && self !== this) {
      return self;
    }
  }