diff --git a/ui/app/components/global-header.js b/ui/app/components/global-header.js index 6ee147b08e72..c90e6457dfd6 100644 --- a/ui/app/components/global-header.js +++ b/ui/app/components/global-header.js @@ -1,5 +1,7 @@ import Component from '@ember/component'; export default Component.extend({ + 'data-test-global-header': true, + onHamburgerClick() {}, }); diff --git a/ui/app/templates/components/gutter-menu.hbs b/ui/app/templates/components/gutter-menu.hbs index 8e0a8ab0ce73..4c182af6f74d 100644 --- a/ui/app/templates/components/gutter-menu.hbs +++ b/ui/app/templates/components/gutter-menu.hbs @@ -49,14 +49,14 @@ {{/if}} -
  • {{#link-to "jobs" activeClass="is-active"}}Jobs{{/link-to}}
  • +
  • {{#link-to "jobs" activeClass="is-active" data-test-gutter-link="jobs"}}Jobs{{/link-to}}
  • diff --git a/ui/app/templates/jobs/index.hbs b/ui/app/templates/jobs/index.hbs index 3985b9f00081..a306f5106169 100644 --- a/ui/app/templates/jobs/index.hbs +++ b/ui/app/templates/jobs/index.hbs @@ -25,7 +25,7 @@ Summary {{/t.head}} {{#t.body key="model.id" as |row|}} - {{job-row data-test-job-row job=row.model onClick=(action "gotoJob" row.model)}} + {{job-row data-test-job-row=row.model.plainId job=row.model onClick=(action "gotoJob" row.model)}} {{/t.body}} {{/list-table}}
    diff --git a/ui/mirage/config.js b/ui/mirage/config.js index 9a419ba7157a..0311a8d94eb6 100644 --- a/ui/mirage/config.js +++ b/ui/mirage/config.js @@ -166,8 +166,9 @@ export default function() { }); this.get('/agent/members', function({ agents, regions }) { + const firstRegion = regions.first(); return { - ServerRegion: regions.first().id, + ServerRegion: firstRegion ? firstRegion.id : null, Members: this.serialize(agents.all()), }; }); diff --git a/ui/tests/acceptance/regions-test.js b/ui/tests/acceptance/regions-test.js new file mode 100644 index 000000000000..9fae4d6a3f61 --- /dev/null +++ b/ui/tests/acceptance/regions-test.js @@ -0,0 +1,210 @@ +import { test } from 'qunit'; +import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; +import JobsList from 'nomad-ui/tests/pages/jobs/list'; +import ClientsList from 'nomad-ui/tests/pages/clients/list'; +import PageLayout from 'nomad-ui/tests/pages/layout'; +import Allocation from 'nomad-ui/tests/pages/allocations/detail'; + +moduleForAcceptance('Acceptance | regions (only one)', { + beforeEach() { + server.create('agent'); + server.create('node'); + server.createList('job', 5); + }, +}); + +test('when there is only one region, the region switcher is not shown in the nav bar', function(assert) { + server.create('region', { id: 'global' }); + + andThen(() => { + JobsList.visit(); + }); + + andThen(() => { + assert.notOk(PageLayout.navbar.regionSwitcher.isPresent, 'No region switcher'); + }); +}); + +test('when the only region is not named "global", the region switcher still is not shown', function(assert) { + server.create('region', { id: 'some-region' }); + + andThen(() => { + JobsList.visit(); + }); + + andThen(() => { + assert.notOk(PageLayout.navbar.regionSwitcher.isPresent, 'No region switcher'); + }); +}); + +test('pages do not include the region query param', function(assert) { + let jobId; + + server.create('region', { id: 'global' }); + + andThen(() => { + JobsList.visit(); + }); + andThen(() => { + assert.equal(currentURL(), '/jobs', 'No region query param'); + }); + andThen(() => { + jobId = JobsList.jobs.objectAt(0).id; + JobsList.jobs.objectAt(0).clickRow(); + }); + andThen(() => { + assert.equal(currentURL(), `/jobs/${jobId}`, 'No region query param'); + }); + andThen(() => { + ClientsList.visit(); + }); + andThen(() => { + assert.equal(currentURL(), '/clients', 'No region query param'); + }); +}); + +test('api requests do not include the region query param', function(assert) { + server.create('region', { id: 'global' }); + + andThen(() => { + JobsList.visit(); + }); + andThen(() => { + JobsList.jobs.objectAt(0).clickRow(); + }); + andThen(() => { + PageLayout.gutter.visitClients(); + }); + andThen(() => { + PageLayout.gutter.visitServers(); + }); + andThen(() => { + server.pretender.handledRequests.forEach(req => { + assert.notOk(req.url.includes('region='), req.url); + }); + }); +}); + +moduleForAcceptance('Acceptance | regions (many)', { + beforeEach() { + server.create('agent'); + server.create('node'); + server.createList('job', 5); + server.create('region', { id: 'global' }); + server.create('region', { id: 'region-2' }); + }, +}); + +test('the region switcher is rendered in the nav bar', function(assert) { + JobsList.visit(); + + andThen(() => { + assert.ok(PageLayout.navbar.regionSwitcher.isPresent, 'Region switcher is shown'); + }); +}); + +test('when on the default region, pages do not include the region query param', function(assert) { + JobsList.visit(); + + andThen(() => { + assert.equal(currentURL(), '/jobs', 'No region query param'); + assert.equal(window.localStorage.nomadActiveRegion, 'global', 'Region in localStorage'); + }); +}); + +test('switching regions sets localStorage and the region query param', function(assert) { + const newRegion = server.db.regions[1].id; + + JobsList.visit(); + + selectChoose('[data-test-region-switcher]', newRegion); + + andThen(() => { + assert.ok( + currentURL().includes(`region=${newRegion}`), + 'New region is the region query param value' + ); + assert.equal(window.localStorage.nomadActiveRegion, newRegion, 'New region in localStorage'); + }); +}); + +test('switching regions to the default region, unsets the region query param', function(assert) { + const startingRegion = server.db.regions[1].id; + const defaultRegion = server.db.regions[0].id; + + JobsList.visit({ region: startingRegion }); + + selectChoose('[data-test-region-switcher]', defaultRegion); + + andThen(() => { + assert.equal(currentURL(), '/jobs', 'No region query param for the default region'); + assert.equal( + window.localStorage.nomadActiveRegion, + defaultRegion, + 'New region in localStorage' + ); + }); +}); + +test('switching regions on deep pages redirects to the application root', function(assert) { + const newRegion = server.db.regions[1].id; + + Allocation.visit({ id: server.db.allocations[0].id }); + + selectChoose('[data-test-region-switcher]', newRegion); + + andThen(() => { + assert.ok(currentURL().includes('/jobs?'), 'Back at the jobs page'); + }); +}); + +test('navigating directly to a page with the region query param sets the application to that region', function(assert) { + const allocation = server.db.allocations[0]; + const region = server.db.regions[1].id; + Allocation.visit({ id: allocation.id, region }); + + andThen(() => { + assert.equal( + currentURL(), + `/allocations/${allocation.id}?region=${region}`, + 'Region param is persisted when navigating straight to a detail page' + ); + assert.equal( + window.localStorage.nomadActiveRegion, + region, + 'Region is also set in localStorage from a detail page' + ); + }); +}); + +test('when the region is not the default region, all api requests include the region query param', function(assert) { + const region = server.db.regions[1].id; + + JobsList.visit({ region }); + + andThen(() => { + JobsList.jobs.objectAt(0).clickRow(); + }); + andThen(() => { + PageLayout.gutter.visitClients(); + }); + andThen(() => { + PageLayout.gutter.visitServers(); + }); + andThen(() => { + const [regionsRequest, defaultRegionRequest, ...appRequests] = server.pretender.handledRequests; + + assert.notOk( + regionsRequest.url.includes('region='), + 'The regions request is made without a region qp' + ); + assert.notOk( + defaultRegionRequest.url.includes('region='), + 'The default region request is made without a region qp' + ); + + appRequests.forEach(req => { + assert.ok(req.url.includes(`region=${region}`), req.url); + }); + }); +}); diff --git a/ui/tests/helpers/module-for-acceptance.js b/ui/tests/helpers/module-for-acceptance.js index 44fe4e0e2f46..05110ce6d24a 100644 --- a/ui/tests/helpers/module-for-acceptance.js +++ b/ui/tests/helpers/module-for-acceptance.js @@ -9,7 +9,7 @@ export default function(name, options = {}) { // Clear session storage (a side effect of token storage) window.sessionStorage.clear(); - // Also clear local storage (a side effect of namespaces) + // Also clear local storage (a side effect of namespaces and regions) window.localStorage.clear(); this.application = startApp(); diff --git a/ui/tests/pages/jobs/list.js b/ui/tests/pages/jobs/list.js index fa05760d93f1..cd59ac7ef675 100644 --- a/ui/tests/pages/jobs/list.js +++ b/ui/tests/pages/jobs/list.js @@ -17,6 +17,7 @@ export default create({ search: fillable('[data-test-jobs-search] input'), jobs: collection('[data-test-job-row]', { + id: attribute('data-test-job-row'), name: text('[data-test-job-name]'), link: attribute('href', '[data-test-job-name] a'), status: text('[data-test-job-status]'), diff --git a/ui/tests/pages/layout.js b/ui/tests/pages/layout.js new file mode 100644 index 000000000000..e276310bb146 --- /dev/null +++ b/ui/tests/pages/layout.js @@ -0,0 +1,31 @@ +import { create, clickable, collection, isPresent, text } from 'ember-cli-page-object'; + +export default create({ + navbar: { + scope: '[data-test-global-header]', + + regionSwitcher: { + scope: '[data-test-region-switcher]', + isPresent: isPresent(), + open: clickable('.ember-power-select-trigger'), + options: collection('.ember-power-select-option', { + label: text(), + }), + }, + }, + + gutter: { + scope: '[data-test-gutter-menu]', + namespaceSwitcher: { + scope: '[data-test-namespace-switcher]', + isPresent: isPresent(), + open: clickable('.ember-power-select-trigger'), + options: collection('.ember-power-select-option', { + label: text(), + }), + }, + visitJobs: clickable('[data-test-gutter-link="jobs"]'), + visitClients: clickable('[data-test-gutter-link="clients"]'), + visitServers: clickable('[data-test-gutter-link="servers"]'), + }, +});