Пример #1
0
    [`@test Slow promises returned from ApplicationRoute#model don't enter LoadingRoute`](assert) {
      let appDeferred = RSVP.defer();

      this.add(
        'route:application',
        Route.extend({
          model() {
            return appDeferred.promise;
          },
        })
      );
      this.add(
        'route:loading',
        Route.extend({
          setupController() {
            assert.ok(false, `shouldn't get here`);
          },
        })
      );

      let promise = this.visit('/').then(() => {
        let text = this.$('#app').text();

        assert.equal(text, 'INDEX', `index template has been rendered`);
      });

      if (this.element) {
        assert.equal(this.element.textContent, '');
      }

      appDeferred.resolve();

      return promise;
    }
Пример #2
0
    ['@test Enter child loading state of pivot route'](assert) {
      let deferred = RSVP.defer();
      this.addTemplate('grandma.loading', 'GMONEYLOADING');

      this.add(
        'route:mom.sally',
        Route.extend({
          setupController() {
            step(assert, 1, 'SallyRoute#setupController');
          },
        })
      );

      this.add(
        'route:grandma.puppies',
        Route.extend({
          model() {
            return deferred.promise;
          },
        })
      );

      return this.visit('/grandma/mom/sally').then(() => {
        assert.equal(this.currentPath, 'grandma.mom.sally', 'Initial route fully loaded');

        let promise = this.visit('/grandma/puppies').then(() => {
          assert.equal(this.currentPath, 'grandma.puppies', 'Finished transition');
        });

        assert.equal(this.currentPath, 'grandma.loading', `in pivot route's child loading state`);
        deferred.resolve();

        return promise;
      });
    }
Пример #3
0
    ['@test Slow promises returned from ApplicationRoute#model enter ApplicationLoadingRoute if present'](
      assert
    ) {
      let appDeferred = RSVP.defer();

      this.add(
        'route:application',
        Route.extend({
          model() {
            return appDeferred.promise;
          },
        })
      );
      let loadingRouteEntered = false;
      this.add(
        'route:application_loading',
        Route.extend({
          setupController() {
            loadingRouteEntered = true;
          },
        })
      );

      let promise = this.visit('/').then(() => {
        assert.equal(this.$('#app').text(), 'INDEX', 'index route loaded');
      });
      assert.ok(loadingRouteEntered, 'ApplicationLoadingRoute was entered');
      appDeferred.resolve();

      return promise;
    }
Пример #4
0
    [`@test Loading actions bubble to root but don't enter substates above pivot `](assert) {
      let sallyDeferred = RSVP.defer();
      let puppiesDeferred = RSVP.defer();

      this.add(
        'route:application',
        Route.extend({
          actions: {
            loading() {
              assert.ok(true, 'loading action received on ApplicationRoute');
            },
          },
        })
      );

      this.add(
        'route:mom.sally',
        Route.extend({
          model() {
            return sallyDeferred.promise;
          },
        })
      );

      this.add(
        'route:grandma.puppies',
        Route.extend({
          model() {
            return puppiesDeferred.promise;
          },
        })
      );

      let promise = this.visit('/grandma/mom/sally');
      assert.equal(this.currentPath, 'index', 'Initial route fully loaded');

      sallyDeferred.resolve();

      promise
        .then(() => {
          assert.equal(this.currentPath, 'grandma.mom.sally', 'transition completed');

          let visit = this.visit('/grandma/puppies');
          assert.equal(
            this.currentPath,
            'grandma.mom.sally',
            'still in initial state because the only loading state is above the pivot route'
          );

          return visit;
        })
        .then(() => {
          this.runTask(() => puppiesDeferred.resolve());

          assert.equal(this.currentPath, 'grandma.puppies', 'Finished transition');
        });

      return promise;
    }
Пример #5
0
    async ['@test Setting a query param during a slow transition should work'](assert) {
      await this.visit('/');

      let deferred = RSVP.defer();
      this.addTemplate('memere.loading', 'MMONEYLOADING');

      this.add(
        'route:grandma',
        Route.extend({
          beforeModel: function() {
            this.transitionTo('memere', 1);
          },
        })
      );

      this.add(
        'route:memere',
        Route.extend({
          queryParams: {
            test: { defaultValue: 1 },
          },
        })
      );

      this.add(
        'route:memere.index',
        Route.extend({
          model() {
            return deferred.promise;
          },
        })
      );

      let promise = runTask(() => this.visit('/grandma')).then(() => {
        assert.equal(this.currentPath, 'memere.index', 'Transition should be complete');
      });
      let memereController = this.getController('memere');

      assert.equal(this.currentPath, 'memere.loading', 'Initial route should be loading');

      memereController.set('test', 3);

      assert.equal(this.currentPath, 'memere.loading', 'Initial route should still be loading');

      assert.equal(
        memereController.get('test'),
        3,
        'Controller query param value should have changed'
      );
      deferred.resolve();

      return promise;
    }
    constructor() {
      super();

      this.aboutDefer = RSVP.defer();
      this.otherDefer = RSVP.defer();
      this.newsDefer = RSVP.defer();
      let _this = this;

      this.router.map(function() {
        this.route('about');
        this.route('other');
        this.route('news');
      });

      this.add(
        'route:about',
        Route.extend({
          model() {
            return _this.aboutDefer.promise;
          },
        })
      );

      this.add(
        'route:other',
        Route.extend({
          model() {
            return _this.otherDefer.promise;
          },
        })
      );

      this.add(
        'route:news',
        Route.extend({
          model() {
            return _this.newsDefer.promise;
          },
        })
      );

      this.addTemplate(
        'application',
        `
      {{outlet}}
      {{link-to 'Index' 'index' id='index-link'}}
      {{link-to 'About' 'about' id='about-link'}}
      {{link-to 'Other' 'other' id='other-link'}}
      {{link-to 'News' 'news' activeClass=false id='news-link'}}
    `
      );
    }
Пример #7
0
    ['@test it should produce a stable DOM when two routes render the same template']() {
      this.router.map(function() {
        this.route('a');
        this.route('b');
      });

      this.add(
        'route:a',
        Route.extend({
          model() {
            return 'A';
          },

          renderTemplate(controller, model) {
            this.render('common', { controller: 'common', model });
          },
        })
      );

      this.add(
        'route:b',
        Route.extend({
          model() {
            return 'B';
          },

          renderTemplate(controller, model) {
            this.render('common', { controller: 'common', model });
          },
        })
      );

      this.add(
        'controller:common',
        Controller.extend({
          prefix: 'common',
        })
      );

      this.addTemplate('common', '{{prefix}} {{model}}');

      return this.visit('/a')
        .then(() => {
          this.assertInnerHTML('common A');
          this.takeSnapshot();
          return this.visit('/b');
        })
        .then(() => {
          this.assertInnerHTML('common B');
          this.assertInvariants();
        });
    }
Пример #8
0
    ['@test Prioritized error substate entry works with preserved-namespaec nested routes'](
      assert
    ) {
      this.addTemplate('foo.bar_error', 'FOOBAR ERROR: {{model.msg}}');
      this.addTemplate('foo.bar', 'YAY');

      this.router.map(function() {
        this.route('foo', function() {
          this.route('bar');
        });
      });

      this.add(
        'route:foo.bar',
        Route.extend({
          model() {
            return RSVP.reject({
              msg: 'did it broke?',
            });
          },
        })
      );

      return this.visit('/').then(() => {
        return this.visit('/foo/bar').then(() => {
          let text = this.$('#app').text();
          assert.equal(
            text,
            'FOOBAR ERROR: did it broke?',
            `foo.bar_error was entered (as opposed to something like foo/foo/bar_error)`
          );
        });
      });
    }
Пример #9
0
    ['@test Slow promises returned from ApplicationRoute#model enter application_loading if template present'](
      assert
    ) {
      let appDeferred = RSVP.defer();

      this.addTemplate(
        'application_loading',
        `
      <div id="toplevel-loading">TOPLEVEL LOADING</div>
    `
      );
      this.add(
        'route:application',
        Route.extend({
          model() {
            return appDeferred.promise;
          },
        })
      );

      let promise = this.visit('/').then(() => {
        let length = this.$('#toplevel-loading').length;
        text = this.$('#app').text();

        assert.equal(length, 0, `top-level loading view has been entirely removed from the DOM`);
        assert.equal(text, 'INDEX', 'index has fully rendered');
      });
      let text = this.$('#toplevel-loading').text();

      assert.equal(text, 'TOPLEVEL LOADING', 'still loading the top level');
      appDeferred.resolve();

      return promise;
    }
Пример #10
0
    async ['@test errors that are bubbled are thrown at a higher level if not handled'](assert) {
      await this.visit('/');

      this.add(
        'route:mom.sally',
        Route.extend({
          model() {
            step(assert, 1, 'MomSallyRoute#model');
            return RSVP.reject({
              msg: 'did it broke?',
            });
          },
          actions: {
            error() {
              step(assert, 2, 'MomSallyRoute#actions.error');
              return true;
            },
          },
        })
      );

      await assert.rejects(
        this.visit('/grandma/mom/sally'),
        function(err) {
          return err.msg == 'did it broke?';
        },
        'Correct error was thrown'
      );
    }
Пример #11
0
    [`@test Don't enter loading route unless either route or template defined`](assert) {
      let deferred = RSVP.defer();

      this.router.map(function() {
        this.route('dummy');
      });
      this.add(
        'route:dummy',
        Route.extend({
          model() {
            return deferred.promise;
          },
        })
      );
      this.addTemplate('dummy', 'DUMMY');

      return this.visit('/').then(() => {
        let promise = this.visit('/dummy').then(() => {
          let text = this.$('#app').text();

          assert.equal(text, 'DUMMY', `dummy template has been rendered`);
        });

        assert.ok(
          this.currentPath !== 'loading',
          `
        loading state not entered
      `
        );
        deferred.resolve();

        return promise;
      });
    }
Пример #12
0
    async ['@test Default error event moves into nested route'](assert) {
      await this.visit('/');

      this.addTemplate('grandma.error', 'ERROR: {{model.msg}}');

      this.add(
        'route:mom.sally',
        Route.extend({
          model() {
            step(assert, 1, 'MomSallyRoute#model');
            return RSVP.reject({
              msg: 'did it broke?',
            });
          },
          actions: {
            error() {
              step(assert, 2, 'MomSallyRoute#actions.error');
              return true;
            },
          },
        })
      );

      return this.visit('/grandma/mom/sally').then(() => {
        step(assert, 3, 'App finished loading');

        let text = this.$('#app').text();

        assert.equal(text, 'GRANDMA ERROR: did it broke?', 'error bubbles');
        assert.equal(this.currentPath, 'grandma.error', 'Initial route fully loaded');
      });
    }
Пример #13
0
      this.runTask(() => {
        this.createApplication();

        this.addTemplate(
          'people',
          `
        <div>
          {{#each model as |person|}}
            <div class="name">{{person.firstName}}</div>
          {{/each}}
        </div>
      `
        );

        this.router.map(function() {
          this.route('people', { path: '/' });
        });

        this.add(
          'route:people',
          Route.extend({
            model: () => this.modelContent,
          })
        );

        this.application.setupForTesting();
      });
Пример #14
0
    ['@test it should produce a stable DOM when the model changes']() {
      this.router.map(function() {
        this.route('color', { path: '/colors/:color' });
      });

      this.add(
        'route:color',
        Route.extend({
          model(params) {
            return params.color;
          },
        })
      );

      this.addTemplate('color', 'color: {{model}}');

      return this.visit('/colors/red')
        .then(() => {
          this.assertInnerHTML('color: red');
          this.takeSnapshot();
          return this.visit('/colors/green');
        })
        .then(() => {
          this.assertInnerHTML('color: green');
          this.assertInvariants();
        });
    }
Пример #15
0
    '@test rejects if there is an unhandled error'(assert) {
      this.addTemplate('parent', 'Parent{{outlet}}');
      this.addTemplate('parent.child', 'Child');

      this.add(
        'route:parent.child',
        Route.extend({
          model() {
            throw Error('Unhandled');
          },
        })
      );
      return this.visit('/')
        .then(() => {
          return this.routerService.recognizeAndLoad('/child');
        })
        .then(
          () => {
            assert.ok(false, 'never');
          },
          err => {
            assert.equal(err.message, 'Unhandled');
          }
        );
    }
Пример #16
0
    '@test rejects if url is not recognized'(assert) {
      this.addTemplate('parent', 'Parent{{outlet}}');
      this.addTemplate('parent.child', 'Child');

      this.add(
        'route:parent.child',
        Route.extend({
          model() {
            return { name: 'child', data: ['stuff'] };
          },
        })
      );
      return this.visit('/')
        .then(() => {
          return this.routerService.recognizeAndLoad('/foo');
        })
        .then(
          () => {
            assert.ok(false, 'never');
          },
          reason => {
            assert.equal(reason, 'URL /foo was not recognized');
          }
        );
    }
Пример #17
0
    ['@test it can access the model provided by the route']() {
      this.add(
        'route:application',
        Route.extend({
          model() {
            return ['red', 'yellow', 'blue'];
          },
        })
      );

      this.addTemplate(
        'application',
        strip`
      <ul>
        {{#each model as |item|}}
          <li>{{item}}</li>
        {{/each}}
      </ul>
    `
      );

      return this.visit('/').then(() => {
        this.assertInnerHTML(strip`
        <ul>
          <li>red</li>
          <li>yellow</li>
          <li>blue</li>
        </ul>
      `);
      });
    }
Пример #18
0
    async [`@test Non-bubbled errors that re-throw aren't swallowed`](assert) {
      await this.visit('/');

      this.add(
        'route:mom.sally',
        Route.extend({
          model() {
            return RSVP.reject({
              msg: 'did it broke?',
            });
          },
          actions: {
            error(err) {
              // returns undefined which is falsey
              throw err;
            },
          },
        })
      );

      await assert.rejects(
        this.visit('/grandma/mom/sally'),
        function(err) {
          return err.msg === 'did it broke?';
        },
        'it broke'
      );
    }
Пример #19
0
        function registerRoute(application, name, callback) {
          let route = EmberRoute.extend({
            activate: callback,
          });

          application.register('route:' + name, route);
        }
Пример #20
0
    async ['@test ApplicationRoute#currentPath reflects loading state path'](assert) {
      await this.visit('/');

      let momDeferred = RSVP.defer();

      this.addTemplate('grandma.loading', 'GRANDMALOADING');

      this.add(
        'route:mom',
        Route.extend({
          model() {
            return momDeferred.promise;
          },
        })
      );

      let promise = runTask(() => this.visit('/grandma/mom')).then(() => {
        text = this.$('#app').text();

        assert.equal(text, 'GRANDMA MOM', `Grandma.mom loaded text is displayed`);
        assert.equal(this.currentPath, 'grandma.mom.index', `currentPath reflects final state`);
      });
      let text = this.$('#app').text();

      assert.equal(text, 'GRANDMA GRANDMALOADING', `Grandma.mom loading text displayed`);

      assert.equal(this.currentPath, 'grandma.loading', `currentPath reflects loading state`);

      momDeferred.resolve();

      return promise;
    }
Пример #21
0
    ['@test it emits a useful backtracking re-render assertion message']() {
      this.router.map(function() {
        this.route('routeWithError');
      });

      this.add(
        'route:routeWithError',
        Route.extend({
          model() {
            return { name: 'Alex' };
          },
        })
      );

      this.addTemplate('routeWithError', 'Hi {{model.name}} {{x-foo person=model}}');

      this.addComponent('x-foo', {
        ComponentClass: Component.extend({
          init() {
            this._super(...arguments);
            this.set('person.name', 'Ben');
          },
        }),
        template: 'Hi {{person.name}} from component',
      });

      let expectedBacktrackingMessage = /modified "model\.name" twice on \[object Object\] in a single render\. It was rendered in "template:my-app\/templates\/routeWithError.hbs" and modified in "component:x-foo"/;

      return this.visit('/').then(() => {
        expectAssertion(() => {
          this.visit('/routeWithError');
        }, expectedBacktrackingMessage);
      });
    }
Пример #22
0
    ['@test Slow promise from a child route of application enters nested loading state'](assert) {
      let turtleDeferred = RSVP.defer();

      this.router.map(function() {
        this.route('turtle');
      });

      this.add(
        'route:application',
        Route.extend({
          setupController() {
            step(assert, 2, 'ApplicationRoute#setupController');
          },
        })
      );

      this.add(
        'route:turtle',
        Route.extend({
          model() {
            step(assert, 1, 'TurtleRoute#model');
            return turtleDeferred.promise;
          },
        })
      );
      this.addTemplate('turtle', 'TURTLE');
      this.addTemplate('loading', 'LOADING');

      let promise = this.visit('/turtle').then(() => {
        text = this.$('#app').text();
        assert.equal(
          text,
          'TURTLE',
          `turtle template has loaded and replaced the loading template`
        );
      });

      let text = this.$('#app').text();
      assert.equal(
        text,
        'LOADING',
        `The Loading template is nested in application template's outlet`
      );

      turtleDeferred.resolve();
      return promise;
    }
    constructor() {
      super();
      this.aboutDefer = RSVP.defer();
      this.otherDefer = RSVP.defer();
      let _this = this;

      this.router.map(function() {
        this.route('parent-route', function() {
          this.route('about');
          this.route('other');
        });
      });
      this.add(
        'route:parent-route.about',
        Route.extend({
          model() {
            return _this.aboutDefer.promise;
          },
        })
      );

      this.add(
        'route:parent-route.other',
        Route.extend({
          model() {
            return _this.otherDefer.promise;
          },
        })
      );

      this.addTemplate(
        'application',
        `
      {{outlet}}
      {{#link-to 'index' tagName='li'}}
        {{link-to 'Index' 'index' id='index-link'}}
      {{/link-to}}
      {{#link-to 'parent-route.about' tagName='li'}}
        {{link-to 'About' 'parent-route.about' id='about-link'}}
      {{/link-to}}
      {{#link-to 'parent-route.other' tagName='li'}}
        {{link-to 'Other' 'parent-route.other' id='other-link'}}
      {{/link-to}}
    `
      );
    }
Пример #24
0
    [`@test Handled errors that are thrown through rejection aren't swallowed`](assert) {
      let handledError;

      this.add(
        'route:mom.sally',
        Route.extend({
          model() {
            step(assert, 1, 'MomSallyRoute#model');
            return RSVP.reject({
              msg: 'did it broke?',
            });
          },
          actions: {
            error(err) {
              step(assert, 2, 'MomSallyRoute#actions.error');
              handledError = err;
              this.transitionTo('mom.this-route-throws');

              return false;
            },
          },
        })
      );

      this.add(
        'route:mom.this-route-throws',
        Route.extend({
          model() {
            step(assert, 3, 'MomThisRouteThrows#model');
            return RSVP.reject(handledError);
          },
        })
      );

      assert.throws(
        () => {
          this.visit('/grandma/mom/sally');
        },
        function(err) {
          return err.msg === 'did it broke?';
        },
        'it broke'
      );

      return this.runLoopSettled();
    }
Пример #25
0
    [`@test visit() rejects if an error occurred during a transition`](assert) {
      this.router.map(function() {
        this.route('a');
        this.route('b', { path: '/b/:b' });
        this.route('c', { path: '/c/:c' });
      });

      this.add(
        'route:a',
        Route.extend({
          afterModel() {
            this.replaceWith('b', 'zomg');
          },
        })
      );

      this.add(
        'route:b',
        Route.extend({
          afterModel(params) {
            this.transitionTo('c', params.b);
          },
        })
      );

      this.add(
        'route:c',
        Route.extend({
          afterModel() {
            throw new Error('transition failure');
          },
        })
      );

      expectAsyncError();

      return this.visit('/a').then(
        () => {
          assert.ok(false, 'It should not resolve the promise');
        },
        error => {
          assert.ok(error instanceof Error, 'It should reject the promise with the boot error');
          assert.equal(error.message, 'transition failure');
        }
      );
    }
Пример #26
0
    '@test returns a RouteInfoWithAttributes for recognized URL'(assert) {
      this.add(
        'route:dynamicWithChild',
        Route.extend({
          model(params) {
            return { name: 'dynamicWithChild', data: params.dynamic_id };
          },
        })
      );
      this.add(
        'route:dynamicWithChild.child',
        Route.extend({
          model(params) {
            return { name: 'dynamicWithChild.child', data: params.child_id };
          },
        })
      );

      return this.visit('/')
        .then(() => {
          return this.routerService.recognizeAndLoad('/dynamic-with-child/123/1?a=b');
        })
        .then(routeInfoWithAttributes => {
          assert.ok(routeInfoWithAttributes);
          let {
            name,
            localName,
            parent,
            attributes,
            paramNames,
            params,
            queryParams,
          } = routeInfoWithAttributes;
          assert.equal(name, 'dynamicWithChild.child');
          assert.equal(localName, 'child');
          assert.equal(parent.name, 'dynamicWithChild');
          assert.deepEqual(params, { child_id: '1' });
          assert.deepEqual(queryParams, { a: 'b' });
          assert.deepEqual(paramNames, ['child_id']);
          assert.deepEqual(attributes, { name: 'dynamicWithChild.child', data: '1' });
          assert.deepEqual(parent.attributes, { name: 'dynamicWithChild', data: '123' });
          assert.deepEqual(parent.paramNames, ['dynamic_id']);
          assert.deepEqual(parent.params, { dynamic_id: '123' });
        });
    }
Пример #27
0
    async [`@test Handled errors that re-throw aren't swallowed`](assert) {
      await this.visit('/');

      let handledError;

      this.add(
        'route:mom.sally',
        Route.extend({
          model() {
            step(assert, 1, 'MomSallyRoute#model');
            return RSVP.reject({
              msg: 'did it broke?',
            });
          },
          actions: {
            error(err) {
              step(assert, 2, 'MomSallyRoute#actions.error');
              handledError = err;
              this.transitionTo('mom.this-route-throws');

              return false;
            },
          },
        })
      );

      this.add(
        'route:mom.this-route-throws',
        Route.extend({
          model() {
            step(assert, 3, 'MomThisRouteThrows#model');
            throw handledError;
          },
        })
      );

      await assert.rejects(
        this.visit('/grandma/mom/sally'),
        function(err) {
          return err.msg === 'did it broke?';
        },
        `it broke`
      );
    }
Пример #28
0
        this.runTask(() => {
          this.createApplication();

          this.router.map(function() {
            this.route('user', { resetNamespace: true }, function() {
              this.route('profile');
              this.route('edit');
            });
          });

          // Emulate a long-running unscheduled async operation.
          let resolveLater = () =>
            new RSVP.Promise(resolve => {
              /*
               * The wait() helper has a 10ms tick. We should resolve() after
               * at least one tick to test whether wait() held off while the
               * async router was still loading. 20ms should be enough.
               */
              later(resolve, { firstName: 'Tom' }, 20);
            });

          this.add(
            'route:user',
            Route.extend({
              model() {
                return resolveLater();
              },
            })
          );

          this.add(
            'route:user.profile',
            Route.extend({
              beforeModel() {
                return resolveLater().then(() => this.transitionTo('user.edit'));
              },
            })
          );

          this.application.setupForTesting();
        });
Пример #29
0
    ['@test Handled errors that bubble can be handled at a higher level'](assert) {
      let handledError;

      this.add(
        'route:mom',
        Route.extend({
          actions: {
            error(err) {
              step(assert, 3, 'MomRoute#actions.error');
              assert.equal(
                err,
                handledError,
                `error handled and rebubbled is handleable at higher route`
              );
            },
          },
        })
      );

      this.add(
        'route:mom.sally',
        Route.extend({
          model() {
            step(assert, 1, 'MomSallyRoute#model');
            return RSVP.reject({
              msg: 'did it broke?',
            });
          },
          actions: {
            error(err) {
              step(assert, 2, 'MomSallyRoute#actions.error');
              handledError = err;

              return true;
            },
          },
        })
      );

      return this.visit('/grandma/mom/sally');
    }
Пример #30
0
    ['@test Prioritized loading substate entry works with auto-generated index routes'](assert) {
      let deferred = RSVP.defer();
      this.addTemplate('foo.index_loading', 'FOO LOADING');
      this.addTemplate('foo.index', 'YAY');
      this.addTemplate('foo', '{{outlet}}');

      this.router.map(function() {
        this.route('foo', function() {
          this.route('bar');
        });
      });

      this.add(
        'route:foo.index',
        Route.extend({
          model() {
            return deferred.promise;
          },
        })
      );
      this.add(
        'route:foo',
        Route.extend({
          model() {
            return true;
          },
        })
      );

      let promise = this.visit('/foo').then(() => {
        text = this.$('#app').text();

        assert.equal(text, 'YAY', 'foo.index was rendered');
      });
      let text = this.$('#app').text();
      assert.equal(text, 'FOO LOADING', 'foo.index_loading was entered');

      deferred.resolve();

      return promise;
    }