diff --git a/ui/core/app/components/form/project/index.hbs b/ui/core/app/components/form/project/index.hbs
index 7541f8ed5..9db695baa 100644
--- a/ui/core/app/components/form/project/index.hbs
+++ b/ui/core/app/components/form/project/index.hbs
@@ -2,6 +2,7 @@
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
+ @error={{true}}
as |form|
>
@@ -10,17 +11,35 @@
@type="name"
@value={{@model.name}}
@label={{t "form.label.name"}}
- @error={{false}}
- readonly={{false}} />
+ @error={{@model.errors.name}}
+ readonly={{false}}
+ as |field|>
+ {{#if @model.errors.name}}
+
+ {{#each @model.errors.name as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+ readonly={{false}}
+ as |field|>
+ {{#if @model.errors.description}}
+
+ {{#each @model.errors.description as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
{
+ // return new Response(400, {}, {
+ // "status": 400,
+ // "code": "invalid_argument",
+ // "message": "The request contained invalid fields.",
+ // "details": {
+ // "request_id": "abc123",
+ // "TraceId": "0123-4567",
+ // "fields": [
+ // {
+ // "name": "name",
+ // "message": "name is required"
+ // },
+ // {
+ // "name": "description",
+ // "message": "The description must not be more than 1 trillion characters long."
+ // }
+ // ]
+ // }
+ // });
+
+ return new Response(409, {}, {
+ "status": 409,
+ "code": "invalid_argument",
+ "message": "The request was awful."
+ });
+ });
this.del('/orgs/:org_id/projects/:id');
}
diff --git a/ui/core/tests/acceptance/projects-test.js b/ui/core/tests/acceptance/projects-test.js
new file mode 100644
index 000000000..a98601b19
--- /dev/null
+++ b/ui/core/tests/acceptance/projects-test.js
@@ -0,0 +1,138 @@
+import { module, test } from 'qunit';
+import { visit, currentURL, click, fillIn, find } from '@ember/test-helpers';
+import { setupApplicationTest } from 'ember-qunit';
+import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
+import a11yAudit from 'ember-a11y-testing/test-support/audit';
+import { Response } from 'miragejs';
+
+module('Acceptance | projects', function (hooks) {
+ setupApplicationTest(hooks);
+ setupMirage(hooks);
+
+ test('visiting projects', async function (assert) {
+ assert.expect(1);
+ await visit('/orgs/1/projects');
+ await a11yAudit();
+ assert.equal(currentURL(), '/orgs/1/projects');
+ });
+
+ test('saving a new project with invalid fields displays error messages', async function (assert) {
+ assert.expect(2);
+ this.server.post('/orgs/:org_id/projects', () => {
+ return new Response(
+ 400,
+ {},
+ {
+ status: 400,
+ code: 'invalid_argument',
+ message: 'The request was invalid.',
+ details: {
+ fields: [
+ {
+ name: 'name',
+ message: 'Name is required.',
+ },
+ ],
+ },
+ }
+ );
+ });
+ await visit('/orgs/1/projects/new');
+ await fillIn('[name="name"]', 'random string');
+ await click('[type="submit"]');
+ assert.ok(
+ find('[role="alert"]').textContent.trim(),
+ 'The request was invalid.',
+ 'Displays primary error message.'
+ );
+ assert.ok(
+ find('.rose-form-error-message').textContent.trim(),
+ 'Name is required.',
+ 'Displays field-level errors.'
+ );
+ });
+
+ test('saving an existing project with invalid fields displays error messages', async function (assert) {
+ assert.expect(2);
+ this.server.createList('project', 1);
+ this.server.patch('/orgs/:org_id/projects/:id', () => {
+ return new Response(
+ 400,
+ {},
+ {
+ status: 400,
+ code: 'invalid_argument',
+ message: 'The request was invalid.',
+ details: {
+ fields: [
+ {
+ name: 'name',
+ message: 'Name is required.',
+ },
+ ],
+ },
+ }
+ );
+ });
+ await visit('/orgs/1/projects/1');
+ await fillIn('[name="name"]', 'random string');
+ await click('[type="submit"]');
+ assert.ok(
+ find('[role="alert"]').textContent.trim(),
+ 'The request was invalid.',
+ 'Displays primary error message.'
+ );
+ assert.ok(
+ find('.rose-form-error-message').textContent.trim(),
+ 'Name is required.',
+ 'Displays field-level errors.'
+ );
+ });
+
+ test('errors are displayed when save project fails', async function (assert) {
+ assert.expect(1);
+ this.server.createList('project', 1);
+ this.server.patch('/orgs/:org_id/projects/:id', () => {
+ return new Response(
+ 490,
+ {},
+ {
+ status: 490,
+ code: 'error',
+ message: 'Oops.',
+ }
+ );
+ });
+ await visit('/orgs/1/projects/1');
+ await fillIn('[name="name"]', 'random string');
+ await click('[type="submit"]');
+ assert.ok(
+ find('[role="alert"]').textContent.trim(),
+ 'Oops.',
+ 'Displays primary error message.'
+ );
+ });
+
+ test('errors are displayed when delete project fails', async function (assert) {
+ assert.expect(1);
+ this.server.createList('project', 1);
+ this.server.del('/orgs/:org_id/projects/:id', () => {
+ return new Response(
+ 490,
+ {},
+ {
+ status: 490,
+ code: 'error',
+ message: 'Oops.',
+ }
+ );
+ });
+ await visit('/orgs/1/projects/1');
+ await click('.rose-button-warning');
+ assert.ok(
+ find('[role="alert"]').textContent.trim(),
+ 'Oops.',
+ 'Displays primary error message.'
+ );
+ });
+});