text: import { RenderingTest, moduleFor } from '../utils/test-case';
  import { Component } from '../utils/helpers';
  import { set } from '@ember/-internals/metal';
  import { runDestroy, runAppend } from 'internal-test-helpers';
  
  moduleFor(
    'ember-glimmer runtime resolver cache',
    class extends RenderingTest {
      '@test a helper definition is only generated once'(assert) {
        this.registerHelper('foo-bar', () => 'foo-bar helper!');
        this.registerHelper('baz-qux', () => 'baz-qux helper!');
  
        // assert precondition
        let state = this.getCacheCounters();
        assert.deepEqual(
          state,
          {
            templateCacheHits: 0,
            templateCacheMisses: 0,
            componentDefinitionCount: 0,
            helperDefinitionCount: 0,
          },
          'precondition'
        );
  
        this.render(
          `
            {{~#if cond~}}
              {{foo-bar}}
            {{~else~}}
              {{baz-qux}}
            {{~/if}}`,
          {
            cond: true,
          }
        );
  
        this.assertText('foo-bar helper!');
        state = this.expectCacheChanges(
          { helperDefinitionCount: 1 },
          state,
          'calculate foo-bar helper only'
        );
  
        // show component-two for the first time
        this.runTask(() => set(this.context, 'cond', false));
  
        this.assertText('baz-qux helper!');
        state = this.expectCacheChanges(
          {
            helperDefinitionCount: 1,
          },
          state,
          'calculate baz-qux helper, misses cache'
        );
  
        // show foo-bar again
        this.runTask(() => set(this.context, 'cond', true));
  
        this.assertText('foo-bar helper!');
        state = this.expectCacheChanges({}, state, 'toggle back to foo-bar cache hit');
  
        // show baz-qux again
        this.runTask(() => set(this.context, 'cond', false));
  
        this.assertText('baz-qux helper!');
        state = this.expectCacheChanges({}, state, 'toggle back to baz-qux cache hit');
      }
  
      '@test a component definition is only generated once'(assert) {
        // static layout
        this.registerComponent('component-one', { template: 'One' });
        this.registerComponent('component-two', {
          ComponentClass: Component.extend(),
          template: 'Two',
        });
  
        // assert precondition
        let state = this.getCacheCounters();
        assert.deepEqual(
          state,
          {
            templateCacheHits: 0,
            templateCacheMisses: 0,
            componentDefinitionCount: 0,
            helperDefinitionCount: 0,
          },
          'precondition'
        );
  
        // show component-one for the first time
        this.render(`{{component componentName}}`, {
          componentName: 'component-one',
        });
  
        this.assertText('One');
        state = this.expectCacheChanges(
          { componentDefinitionCount: 1 },
          state,
          'test case component and component-one no change'
        );
  
        // show component-two for the first time
        this.runTask(() => set(this.context, 'componentName', 'component-two'));
  
        this.assertText('Two');
        state = this.expectCacheChanges(
          { componentDefinitionCount: 1 },
          state,
          'component-two first render'
        );
  
        // show component-one again
        this.runTask(() => set(this.context, 'componentName', 'component-one'));
  
        this.assertText('One');
        state = this.expectCacheChanges({}, state, 'toggle back to component-one no change');
  
        // show component-two again
        this.runTask(() => set(this.context, 'componentName', 'component-two'));
  
        this.assertText('Two');
        state = this.expectCacheChanges({}, state, 'toggle back to component-two no change');
      }
  
      ['@test each template is only compiled once'](assert) {
        // static layout
        this.registerComponent('component-one', { template: 'One' });
  
        // test directly import template factory onto late bound layout
        let Two = Component.extend({
          layout: this.compile('Two'),
        });
        this.registerComponent('component-two', { ComponentClass: Two });
  
        // inject layout onto component, share layout with component-one
        this.registerComponent('root-component', { ComponentClass: Component });
        this.owner.inject('component:root-component', 'layout', 'template:components/component-one');
  
        // template instance shared between to template managers
        let rootFactory = this.owner.factoryFor('component:root-component');
  
        // assert precondition
        let state = this.getCacheCounters();
        assert.deepEqual(
          state,
          {
            templateCacheHits: 0,
            templateCacheMisses: 0,
            componentDefinitionCount: 0,
            helperDefinitionCount: 0,
          },
          'precondition'
        );
  
        // show component-one for the first time
        this.render(
          `
      {{~#if cond~}}
        {{component-one}}
      {{~else~}}
        {{component-two}}
      {{~/if}}`,
          {
            cond: true,
          }
        );
  
        this.assertText('One');
        state = this.expectCacheChanges(
          { componentDefinitionCount: 1 },
          state,
          'test case component and component-one no change'
        );
  
        // show component-two for the first time
        this.runTask(() => set(this.context, 'cond', false));
  
        this.assertText('Two');
        state = this.expectCacheChanges(
          {
            templateCacheMisses: 1,
            componentDefinitionCount: 1,
          },
          state,
          'component-two first render misses template cache'
        );
  
        // show component-one again
        this.runTask(() => set(this.context, 'cond', true));
  
        this.assertText('One');
        state = this.expectCacheChanges({}, state, 'toggle back to component-one no change');
  
        // show component-two again
        this.runTask(() => set(this.context, 'cond', false));
  
        this.assertText('Two');
        state = this.expectCacheChanges(
          {
            templateCacheHits: 1,
          },
          state,
          'toggle back to component-two hits template cache'
        );
  
        // render new root append
        let root = rootFactory.create();
        try {
          runAppend(root);
          this.assertText('TwoOne');
          // roots have different capabilities so this will hit
          state = this.expectCacheChanges({}, state, 'append root with component-one no change');
  
          // render new root append
          let root2 = rootFactory.create();
          try {
            runAppend(root2);
            this.assertText('TwoOneOne');
            state = this.expectCacheChanges({}, state, 'append another root no change');
          } finally {
            runDestroy(root2);
          }
        } finally {
          runDestroy(root);
        }
      }
  
      getCacheCounters() {
        let {
          templateCacheHits,
          templateCacheMisses,
          componentDefinitionCount,
          helperDefinitionCount,
        } = this.runtimeResolver;
  
        return {
          templateCacheHits,
          templateCacheMisses,
          componentDefinitionCount,
          helperDefinitionCount,
        };
      }
  
      expectCacheChanges(expected, lastState, message) {
        let state = this.getCacheCounters();
        let actual = diff(state, lastState);
        this.assert.deepEqual(actual, expected, message);
        return state;
      }
    }
  );
  
  function diff(state, lastState) {
    let res = {};
    Object.keys(state).forEach(key => {
      let delta = state[key] - lastState[key];
      if (delta !== 0) {
        res[key] = state[key] - lastState[key];
      }
    });
    return res;
  }
  