report poll saving errors using a modal (#729)
This commit is contained in:
parent
0c4ef6fc5b
commit
88a51964f1
18 changed files with 464 additions and 33 deletions
|
@ -1,11 +1,22 @@
|
|||
<BsButton
|
||||
@type="primary"
|
||||
{{!
|
||||
Due to a bug in Ember, conditional modifiers cannot be used with the "on"
|
||||
modifier. Need to always apply the modifier and fallback to a noop function
|
||||
(returned by noop helper) instead of only applying the modifier when needed.
|
||||
See https://github.com/emberjs/ember.js/issues/19869.
|
||||
}}
|
||||
{{on "click" (if @onClick @onClick (noop))}}
|
||||
class="cr-steps-bottom-nav__button cr-steps-bottom-nav__next-button next"
|
||||
type="submit"
|
||||
...attributes
|
||||
>
|
||||
<span class="cr-steps-bottom-nav__label">
|
||||
{{#if (has-block)}}
|
||||
{{yield}}
|
||||
{{else}}
|
||||
{{t "action.save"}}
|
||||
{{/if}}
|
||||
</span>
|
||||
|
||||
{{#if @isPending}}
|
||||
|
|
|
@ -4,8 +4,12 @@ interface SaveButtonSignature {
|
|||
Args: {
|
||||
Named: {
|
||||
isPending: boolean;
|
||||
onClick?: () => void;
|
||||
};
|
||||
};
|
||||
Blocks: {
|
||||
default: [];
|
||||
};
|
||||
Element: HTMLButtonElement;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import type RouterService from '@ember/routing/router-service';
|
|||
import type { CreateSettingsRouteModel } from 'croodle/routes/create/settings';
|
||||
import type IntlService from 'ember-intl/services/intl';
|
||||
import type FlashMessagesService from 'ember-cli-flash/services/flash-messages';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class CreateSettings extends Controller {
|
||||
@service declare flashMessages: FlashMessagesService;
|
||||
|
@ -17,6 +18,8 @@ export default class CreateSettings extends Controller {
|
|||
|
||||
declare model: CreateSettingsRouteModel;
|
||||
|
||||
@tracked savingPollFailed = false;
|
||||
|
||||
get anonymousUser() {
|
||||
return this.model.anonymousUser;
|
||||
}
|
||||
|
@ -94,7 +97,7 @@ export default class CreateSettings extends Controller {
|
|||
}
|
||||
|
||||
@action
|
||||
async submit() {
|
||||
async createPoll() {
|
||||
const { model } = this;
|
||||
const {
|
||||
anonymousUser,
|
||||
|
@ -158,15 +161,20 @@ export default class CreateSettings extends Controller {
|
|||
);
|
||||
|
||||
// redirect to new poll
|
||||
await this.router.transitionTo('poll', poll.id, {
|
||||
await this.router.transitionTo('poll.participation', poll.id, {
|
||||
queryParams: {
|
||||
encryptionKey,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
this.flashMessages.danger('error.poll.savingFailed');
|
||||
this.savingPollFailed = true;
|
||||
|
||||
throw err;
|
||||
reportError(err);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
resetSavingPollFailedState() {
|
||||
this.savingPollFailed = false;
|
||||
}
|
||||
}
|
||||
|
|
13
app/helpers/noop.ts
Normal file
13
app/helpers/noop.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { helper } from '@ember/component/helper';
|
||||
|
||||
const noop = helper(() => {
|
||||
return () => {};
|
||||
});
|
||||
|
||||
export default noop;
|
||||
|
||||
declare module '@glint/environment-ember-loose/registry' {
|
||||
export default interface Registry {
|
||||
noop: typeof noop;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
@formLayout="horizontal"
|
||||
@model={{this}}
|
||||
@onInvalid={{(scroll-first-invalid-element-into-view-port)}}
|
||||
@onSubmit={{this.submit}}
|
||||
@onSubmit={{this.createPoll}}
|
||||
novalidate
|
||||
as |form|
|
||||
>
|
||||
|
@ -78,5 +78,36 @@
|
|||
<BackButton @onClick={{this.previousPage}} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BsModal
|
||||
@onHidden={{this.resetSavingPollFailedState}}
|
||||
@onSubmit={{form.submit}}
|
||||
@open={{this.savingPollFailed}}
|
||||
data-test-modal="saving-failed"
|
||||
as |modal|
|
||||
>
|
||||
<modal.header
|
||||
@closeButton={{false}}
|
||||
@title={{t "error.poll.savingFailed.title"}}
|
||||
/>
|
||||
<modal.body>
|
||||
<p>
|
||||
{{t "error.poll.savingFailed.description"}}
|
||||
</p>
|
||||
</modal.body>
|
||||
<modal.footer>
|
||||
<BsButton @onClick={{modal.close}} data-test-button="abort">
|
||||
{{t "action.abort"}}
|
||||
</BsButton>
|
||||
<SaveButton
|
||||
@isPending={{form.isSubmitting}}
|
||||
@onClick={{modal.submit}}
|
||||
data-test-button="retry"
|
||||
type="button"
|
||||
>
|
||||
{{t "modal.save-retry.button-retry"}}
|
||||
</SaveButton>
|
||||
</modal.footer>
|
||||
</BsModal>
|
||||
</BsForm>
|
||||
</div>
|
185
package-lock.json
generated
185
package-lock.json
generated
|
@ -62,6 +62,7 @@
|
|||
"ember-power-calendar-luxon": "^0.5.0",
|
||||
"ember-qunit": "^7.0.0",
|
||||
"ember-resolver": "^11.0.1",
|
||||
"ember-sinon-qunit": "^7.4.0",
|
||||
"ember-source": "~5.4.0",
|
||||
"ember-template-lint": "^5.11.2",
|
||||
"ember-test-selectors": "^6.0.0",
|
||||
|
@ -82,6 +83,7 @@
|
|||
"qunit-dom": "^3.0.0",
|
||||
"release-it": "^16.0.0",
|
||||
"sass": "^1.19.0",
|
||||
"sinon": "^17.0.1",
|
||||
"sjcl": "^1.0.8",
|
||||
"stylelint": "^15.10.3",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
|
@ -9679,6 +9681,50 @@
|
|||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinonjs/commons": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz",
|
||||
"integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinonjs/fake-timers": {
|
||||
"version": "11.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz",
|
||||
"integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinonjs/samsam": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz",
|
||||
"integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^2.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"type-detect": "^4.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz",
|
||||
"integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinonjs/text-encoding": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz",
|
||||
"integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||
|
@ -10214,6 +10260,21 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sinon": {
|
||||
"version": "10.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.20.tgz",
|
||||
"integrity": "sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/sinonjs__fake-timers": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sinonjs__fake-timers": {
|
||||
"version": "8.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.4.tgz",
|
||||
"integrity": "sha512-GDV68H0mBSN449sa5HEj51E0wfpVQb8xNSMzxf/PrypMFcLTMwJMOM/cgXiv71Mq5drkOQmUGvL1okOZcu6RrQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/sizzle": {
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.5.tgz",
|
||||
|
@ -38972,6 +39033,21 @@
|
|||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/ember-sinon-qunit": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ember-sinon-qunit/-/ember-sinon-qunit-7.4.0.tgz",
|
||||
"integrity": "sha512-BcH2scgJ4Vpq5Fnjeq5Z2ESnHLsmcfFRaq/gOujy3my+8w7WTtrHyaUgWzmd5mLw+tfCYssAUEalQhk1ZNpV+g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@embroider/addon-shim": "^1.8.6",
|
||||
"@types/sinon": "^10.0.19"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ember-source": ">=3.28.0",
|
||||
"qunit": "^2.0.0",
|
||||
"sinon": "^15.0.3 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ember-source": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ember-source/-/ember-source-5.4.0.tgz",
|
||||
|
@ -46825,6 +46901,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/just-extend": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz",
|
||||
"integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
@ -48842,6 +48924,61 @@
|
|||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nise": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz",
|
||||
"integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^2.0.0",
|
||||
"@sinonjs/fake-timers": "^10.0.2",
|
||||
"@sinonjs/text-encoding": "^0.7.1",
|
||||
"just-extend": "^4.0.2",
|
||||
"path-to-regexp": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nise/node_modules/@sinonjs/commons": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz",
|
||||
"integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/nise/node_modules/@sinonjs/fake-timers": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
|
||||
"integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nise/node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz",
|
||||
"integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/nise/node_modules/isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nise/node_modules/path-to-regexp": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
|
||||
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isarray": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/no-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
||||
|
@ -52764,6 +52901,45 @@
|
|||
"integrity": "sha512-C2WEK/Z3HoSFbYq8tI7ni3eOo/NneSPRoPpcM7WdLjFOArFuyXEjAoCdOC3DgMfRyziZQ1hCNR4mrNdWEvD0og==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sinon": {
|
||||
"version": "17.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz",
|
||||
"integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^3.0.0",
|
||||
"@sinonjs/fake-timers": "^11.2.2",
|
||||
"@sinonjs/samsam": "^8.0.0",
|
||||
"diff": "^5.1.0",
|
||||
"nise": "^5.1.5",
|
||||
"supports-color": "^7.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/sinon"
|
||||
}
|
||||
},
|
||||
"node_modules/sinon/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/sinon/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/sjcl": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/sjcl/-/sjcl-1.0.8.tgz",
|
||||
|
@ -55552,6 +55728,15 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
|
||||
|
|
|
@ -81,6 +81,7 @@
|
|||
"ember-power-calendar-luxon": "^0.5.0",
|
||||
"ember-qunit": "^7.0.0",
|
||||
"ember-resolver": "^11.0.1",
|
||||
"ember-sinon-qunit": "^7.4.0",
|
||||
"ember-source": "~5.4.0",
|
||||
"ember-template-lint": "^5.11.2",
|
||||
"ember-test-selectors": "^6.0.0",
|
||||
|
@ -101,6 +102,7 @@
|
|||
"qunit-dom": "^3.0.0",
|
||||
"release-it": "^16.0.0",
|
||||
"sass": "^1.19.0",
|
||||
"sinon": "^17.0.1",
|
||||
"sjcl": "^1.0.8",
|
||||
"stylelint": "^15.10.3",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
|
|
|
@ -26,6 +26,7 @@ import pageCreateSettings from 'croodle/tests/pages/create/settings';
|
|||
import pagePollParticipation from 'croodle/tests/pages/poll/participation';
|
||||
import asyncThrowsAssertion from '../assertions/async-throws';
|
||||
import { calendarSelect } from 'ember-power-calendar/test-support';
|
||||
import sinon from 'sinon';
|
||||
|
||||
module('Acceptance | create a poll', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
|
@ -141,25 +142,16 @@ module('Acceptance | create a poll', function (hooks) {
|
|||
'available answers selection has autofocus',
|
||||
);
|
||||
|
||||
// simulate temporary server error
|
||||
this.server.logging = true;
|
||||
this.server.post('/polls', undefined, 503);
|
||||
|
||||
await assert.asyncThrows(async () => {
|
||||
await pageCreateSettings.save();
|
||||
}, 'Unexpected server-side error. Server responded with 503 (Service Unavailable)');
|
||||
assert.strictEqual(currentRouteName(), 'create.settings');
|
||||
|
||||
// simulate server is available again
|
||||
// defer creation for testing loading spinner
|
||||
let resolveSubmission;
|
||||
let resolveSubmissionWith;
|
||||
this.server.post('/polls', function (schema) {
|
||||
return new Promise((resolve) => {
|
||||
let attrs = this.normalizedRequestAttrs();
|
||||
|
||||
resolveSubmission = resolve;
|
||||
resolveSubmissionWith = schema.polls.create(attrs);
|
||||
resolveSubmission = () => {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const poll = schema.polls.create(attrs);
|
||||
resolve(poll);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -171,7 +163,7 @@ module('Acceptance | create a poll', function (hooks) {
|
|||
});
|
||||
assert.ok(true, 'loading spinner is shown');
|
||||
|
||||
resolveSubmission(resolveSubmissionWith);
|
||||
resolveSubmission();
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(currentRouteName(), 'poll.participation');
|
||||
|
@ -958,6 +950,170 @@ module('Acceptance | create a poll', function (hooks) {
|
|||
assert.strictEqual(currentRouteName(), 'create.index');
|
||||
});
|
||||
|
||||
test('informs user if saving fails', async function (assert) {
|
||||
const reportErrorFake = sinon.replace(window, 'reportError', sinon.fake());
|
||||
|
||||
await pageCreateIndex.visit();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'create.index',
|
||||
'assumption: can open start page of poll creation',
|
||||
);
|
||||
|
||||
await pageCreateIndex.next();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'create.meta',
|
||||
'assumption: can go to title and description input step',
|
||||
);
|
||||
|
||||
await pageCreateMeta.title('foo').next();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'create.options',
|
||||
'assumption: can go to options input step',
|
||||
);
|
||||
|
||||
await pageCreateOptions.selectDates([new Date()]);
|
||||
await pageCreateOptions.next();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'create.options-datetime',
|
||||
'assumption: can go to times input for dates after selecting one day',
|
||||
);
|
||||
|
||||
await pageCreateOptionsDatetime.next();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'create.settings',
|
||||
'assumption: can go to settings page',
|
||||
);
|
||||
|
||||
// simulate temporary server error
|
||||
this.server.logging = true;
|
||||
this.server.post('/polls', undefined, 503);
|
||||
|
||||
await click('form button[type="submit"]');
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'create.settings',
|
||||
'user stays at settings route if saving fails',
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-modal="saving-failed"]')
|
||||
.isVisible(
|
||||
'modal is shown informing the user that saving the poll failed',
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-modal="saving-failed"] .modal-header')
|
||||
.hasText(
|
||||
t('error.poll.savingFailed.title'),
|
||||
'modal has a meaningful title',
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-modal="saving-failed"] .modal-body')
|
||||
.hasText(
|
||||
t('error.poll.savingFailed.description'),
|
||||
'modal has a meaningful body',
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-modal="saving-failed"] .modal-footer button')
|
||||
.exists({ count: 2 }, 'modal has two buttons');
|
||||
assert
|
||||
.dom(
|
||||
'[data-test-modal="saving-failed"] .modal-footer button[data-test-button="abort"]',
|
||||
)
|
||||
.hasText(t('action.abort'), 'abort button has meaningful text');
|
||||
assert
|
||||
.dom(
|
||||
'[data-test-modal="saving-failed"] .modal-footer button[data-test-button="retry"]',
|
||||
)
|
||||
.hasText(
|
||||
t('modal.save-retry.button-retry'),
|
||||
'retry button has meaningful text',
|
||||
);
|
||||
assert.ok(reportErrorFake.calledOnce, 'error is reported to console');
|
||||
assert.ok(
|
||||
reportErrorFake.firstCall.args[0] instanceof Error,
|
||||
'reported error is an instance of Error',
|
||||
);
|
||||
assert.strictEqual(
|
||||
reportErrorFake.firstCall.args[0].message,
|
||||
'Unexpected server-side error. Server responded with 503 (Service Unavailable)',
|
||||
'reported error has meaningful error message',
|
||||
);
|
||||
|
||||
await click(
|
||||
'[data-test-modal="saving-failed"] button[data-test-button="retry"]',
|
||||
);
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'create.settings',
|
||||
'user stays at settings route if saving failed even on retry',
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-modal="saving-failed"]')
|
||||
.isVisible('modal is still shown if retry fails');
|
||||
assert.ok(
|
||||
reportErrorFake.calledTwice,
|
||||
'error is reported to console on failed retry',
|
||||
);
|
||||
|
||||
await click(
|
||||
'[data-test-modal="saving-failed"] button[data-test-button="abort"]',
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-modal="saving-failed"]')
|
||||
.isNotVisible('user can close the modal that saving failed');
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'create.settings',
|
||||
'user stays at settings route if closing the modal',
|
||||
);
|
||||
|
||||
await click('form button[type="submit"]');
|
||||
assert
|
||||
.dom('[data-test-modal="saving-failed"]')
|
||||
.isVisible('modal is visible again if saving fails again');
|
||||
assert.ok(
|
||||
reportErrorFake.calledThrice,
|
||||
'error is reported to console on failed retry',
|
||||
);
|
||||
|
||||
// simulate server is available again
|
||||
// defer creation for testing loading spinner
|
||||
let resolveSubmission;
|
||||
this.server.post('/polls', function (schema) {
|
||||
return new Promise((resolve) => {
|
||||
resolveSubmission = () => {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const poll = schema.polls.create(attrs);
|
||||
resolve(poll);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
click('[data-test-modal="saving-failed"] button[data-test-button="retry"]');
|
||||
|
||||
// shows loading spinner while saving
|
||||
await waitFor(
|
||||
'[data-test-modal="saving-failed"] button[data-test-button="retry"] .spinner-border',
|
||||
{
|
||||
timeoutMessage: 'timeout while waiting for loading spinner to appear',
|
||||
},
|
||||
);
|
||||
assert.ok(true, 'loading spinner is shown');
|
||||
|
||||
resolveSubmission();
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'poll.participation',
|
||||
'user is transitioned to poll participation page after successful retry',
|
||||
);
|
||||
});
|
||||
|
||||
module('validation', function () {
|
||||
test('validates user input when creating a poll with dates and times', async function (assert) {
|
||||
const day = DateTime.now();
|
||||
|
|
|
@ -4,6 +4,7 @@ import * as QUnit from 'qunit';
|
|||
import { setApplication } from '@ember/test-helpers';
|
||||
import { setup } from 'qunit-dom';
|
||||
import { start } from 'ember-qunit';
|
||||
import setupSinon from 'ember-sinon-qunit';
|
||||
|
||||
document.addEventListener(
|
||||
'securitypolicyviolation',
|
||||
|
@ -20,4 +21,6 @@ setApplication(Application.create(config.APP));
|
|||
|
||||
setup(QUnit.assert);
|
||||
|
||||
setupSinon();
|
||||
|
||||
start();
|
||||
|
|
|
@ -107,7 +107,9 @@ error:
|
|||
expired: 'El sondeig ha caducat i ha estat eliminat.'
|
||||
typo: 'Hi ha un error tipogràfic en la URL. Pots tornar a comprovar
|
||||
el que has posat, especialment la part anterior al signe d''interrogació.'
|
||||
savingFailed: 'No hem pogut guardar el sondeig, si us plau, intenta''l de
|
||||
savingFailed:
|
||||
title: 'Saving failed'
|
||||
description: 'No hem pogut guardar el sondeig, si us plau, intenta''l de
|
||||
nou en uns segons.'
|
||||
generic:
|
||||
unexpected:
|
||||
|
|
|
@ -112,8 +112,11 @@ error:
|
|||
typo: 'Die URL ist fehlerhaft. Bitte prüfe, dass die URL vollständig
|
||||
und korrekt ist. Achte dabei insbesondere auf den Teil vor dem
|
||||
Fragezeichen.'
|
||||
savingFailed: 'Die Umfrage konnte nicht gespeichert werden. Bitte versuche
|
||||
es in einigen Sekunden erneut.'
|
||||
savingFailed:
|
||||
title: 'Speichern fehlgeschlagen'
|
||||
description: 'Die Umfrage konnte nicht gespeichert werden. Bitte prüfe deine
|
||||
Internetverbindung und versuche es erneut. Sollte der Fehler weiterhin
|
||||
auftreten, wende dich bitte an den Administrator der Seite.'
|
||||
generic:
|
||||
unexpected:
|
||||
title: 'Ein unerwarteter Fehler ist aufgetreten'
|
||||
|
|
|
@ -107,7 +107,10 @@ error:
|
|||
expired: 'The poll is expired and has been deleted.'
|
||||
typo: 'There is a typo in the URL. You may want to double-check it
|
||||
– especially the part before the question mark.'
|
||||
savingFailed: 'The poll could not be saved. Please try again in a few seconds.'
|
||||
savingFailed:
|
||||
title: 'Saving failed'
|
||||
description: 'The poll could not be saved. Please check your network connection
|
||||
and try again. Please contact the site administrator if the problem persists.'
|
||||
generic:
|
||||
unexpected:
|
||||
title: 'An unexpected error occured'
|
||||
|
|
|
@ -109,7 +109,9 @@ error:
|
|||
expired: 'La encuesta ha expirado y ha sido borrada.'
|
||||
typo: 'Hay un error al teclear la URL. Por favor, comprueba que la
|
||||
has escrito bien, sobre todo la parte anterior al signo de interrogación.'
|
||||
savingFailed: 'La encuesta no pude ser guardada. Por favor prueba de nuevo
|
||||
savingFailed:
|
||||
title: 'Saving failed'
|
||||
description: 'La encuesta no pude ser guardada. Por favor prueba de nuevo
|
||||
en unos segundos.'
|
||||
generic:
|
||||
unexpected:
|
||||
|
|
|
@ -108,7 +108,9 @@ error:
|
|||
expired: 'Le sondage a expiré et a été supprimé.'
|
||||
typo: "Il y a une faute de frappe dans l'URL. Vous voudrez peut-être\
|
||||
\ revérifier - en particulier la partie avant le point d'interrogation."
|
||||
savingFailed: "Le sondage n'a pas pu être enregistré. Veuillez réessayer dans\
|
||||
savingFailed:
|
||||
title: "Saving failed"
|
||||
description: "Le sondage n'a pas pu être enregistré. Veuillez réessayer dans\
|
||||
\ quelques secondes."
|
||||
generic:
|
||||
unexpected:
|
||||
|
|
|
@ -108,7 +108,9 @@ error:
|
|||
expired: 'Il sondaggio è scaduto ed è stato rimosso.'
|
||||
typo: 'L''URL è errato. Prova a ricontrollarlo, specialmente la parte
|
||||
prima del punto interrogativo.'
|
||||
savingFailed: 'Non è stato possible salvare il sondaggio. Riprova tra qualche
|
||||
savingFailed:
|
||||
title: "Saving failed"
|
||||
description: 'Non è stato possible salvare il sondaggio. Riprova tra qualche
|
||||
secondo.'
|
||||
generic:
|
||||
unexpected:
|
||||
|
|
|
@ -87,7 +87,9 @@ error:
|
|||
delen før spørsmålstegnet.
|
||||
expired: Avstemmingen er utløpt, og har blitt slettet.
|
||||
title: Kunne ikke finne avstemming
|
||||
savingFailed: Kunne ikke lagre avstemming. Prøv igjen om noen sekunder.
|
||||
savingFailed:
|
||||
title: Saving failed
|
||||
description: Kunne ikke lagre avstemming. Prøv igjen om noen sekunder.
|
||||
decryptionFailed:
|
||||
description: Dekryptering av avstemmingsdata mislyktes. Dette skjer antageligvis
|
||||
fordi krypteringsnøkkelen ikke er riktig. Dobbeltsjekk nettadressen
|
||||
|
|
|
@ -15,6 +15,7 @@ type BsFormComponent = ComponentLike<{
|
|||
{
|
||||
element: BsFormElementComponent;
|
||||
isSubmitting: boolean;
|
||||
submit: () => void;
|
||||
},
|
||||
];
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ declare module '@glint/environment-ember-loose/registry' {
|
|||
closeButton?: boolean;
|
||||
footer?: boolean;
|
||||
keyboard?: boolean;
|
||||
onHide?: () => void;
|
||||
onHidden?: () => void;
|
||||
onSubmit?: () => void;
|
||||
open: boolean;
|
||||
|
|
Loading…
Reference in a new issue