Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Enregistrer la confirmation de lecture des écrans d'instructions sur Pix App (PIX-12907). #9443

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/lib/domain/models/CertificationCandidate.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const certificationCandidateParticipationJoiSchema = Joi.object({
.empty(null),
prepaymentCode: Joi.string().allow(null).optional(),
subscriptions: Joi.array().items(subscriptionSchema).unique('type').required(),
hasSeenCertificationInstructions: Joi.boolean().optional(),
});

class CertificationCandidate {
Expand Down Expand Up @@ -84,6 +85,7 @@ class CertificationCandidate {
billingMode = null,
prepaymentCode = null,
subscriptions = [],
hasSeenCertificationInstructions = false,
} = {}) {
this.id = id;
this.firstName = firstName;
Expand All @@ -107,6 +109,7 @@ class CertificationCandidate {
this.subscriptions = subscriptions;
this.billingMode = billingMode;
this.prepaymentCode = prepaymentCode;
this.hasSeenCertificationInstructions = hasSeenCertificationInstructions;

Object.defineProperty(this, 'complementaryCertification', {
enumerable: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const serialize = function (certificationCandidates) {
'complementaryCertification',
'billingMode',
'prepaymentCode',
'hasSeenCertificationInstructions',
],
}).serialize(certificationCandidates);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('Unit | Domain | Models | Certification Candidate', function () {
resultRecipientEmail: undefined,
complementaryCertification: null,
subscriptions: [coreSubscription],
hasSeenCertificationInstructions: false,
};
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ApplicationAdapter from './application';

export default class AccountRecoveryDemandAdapter extends ApplicationAdapter {
export default class CertificationCandidateSubscription extends ApplicationAdapter {
urlForFindRecord(id) {
return `${this.host}/${this.namespace}/certification-candidates/${id}/subscriptions`;
}
Expand Down
11 changes: 11 additions & 0 deletions mon-pix/app/adapters/certification-candidate.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,15 @@ export default class CertificationCandidate extends ApplicationAdapter {

return url;
}

urlForUpdateRecord(id, modelName, { adapterOptions }) {
const url = super.urlForUpdateRecord(...arguments);

if (adapterOptions && adapterOptions.hasSeenCertificationInstructions) {
delete adapterOptions.hasSeenCertificationInstructions;
return `${url}/validate-certification-instructions`;
}

return url;
}
}
19 changes: 13 additions & 6 deletions mon-pix/app/components/certification-instructions/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,27 @@ export default class Steps extends Component {
}

@action
nextStep() {
async nextStep() {
if (this.pageId < this.pageCount) {
this.pageId = this.pageId + 1;
}

if (this.isConfirmationCheckboxChecked) {
this.router.transitionTo('authenticated.certifications.start', this.args.candidateId, {
queryParams: {
isConfirmationCheckboxChecked: this.isConfirmationCheckboxChecked,
},
});
await this.submit();

this.router.transitionTo('authenticated.certifications.start', this.args.candidate.id);
}
}

@action
async submit() {
await this.args.candidate.save({
adapterOptions: {
hasSeenCertificationInstructions: true,
},
});
}

@action
enableNextButton(checked) {
this.isConfirmationCheckboxChecked = checked;
Expand Down
1 change: 1 addition & 0 deletions mon-pix/app/models/certification-candidate.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default class CertificationCandidate extends Model {
@attr('string') firstName;
@attr('string') lastName;
@attr('date-only') birthdate;
@attr('boolean') hasSeenCertificationInstructions;

// references
@attr('number') sessionId;
Expand Down
12 changes: 11 additions & 1 deletion mon-pix/app/routes/authenticated/certifications/information.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,18 @@ import { service } from '@ember/service';

export default class InformationRoute extends Route {
@service store;
@service router;

async model(params) {
return await this.store.findRecord('certification-candidate-subscription', params.certification_candidate_id);
const certificationCandidate = await this.store.peekRecord(
'certification-candidate',
params.certification_candidate_id,
);

if (!certificationCandidate) {
this.router.replaceWith('authenticated.certifications.join');
}

return certificationCandidate;
}
}
14 changes: 8 additions & 6 deletions mon-pix/app/routes/authenticated/certifications/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ export default class StartRoute extends Route {
@service store;
@service router;
@service featureToggles;
hasSeenCertificationInstructions = false;

beforeModel(transition) {
this.hasSeenCertificationInstructions = transition.to.queryParams.isConfirmationCheckboxChecked;
}

async model(params) {
const certificationCandidateSubscription = await this.store.findRecord(
'certification-candidate-subscription',
params.certification_candidate_id,
);

const certificationCandidate = await this.store.peekRecord(
'certification-candidate',
params.certification_candidate_id,
);

const hasSeenCertificationInstructions = certificationCandidate?.hasSeenCertificationInstructions;

if (
!this.hasSeenCertificationInstructions &&
!hasSeenCertificationInstructions &&
certificationCandidateSubscription.isSessionVersion3 &&
this.featureToggles.featureToggles.areV3InfoScreensEnabled
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
<h1 class="instructions-header__title">{{t "pages.certification-instructions.title"}}</h1>
</header>
<PixBlock @shadow="heavy" class="instructions-step">
<CertificationInstructions::Steps @candidateId={{this.model.id}} />
<CertificationInstructions::Steps @candidate={{this.model}} />
</PixBlock>
</main>
6 changes: 6 additions & 0 deletions mon-pix/mirage/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import putTutorialEvaluation from './routes/put-tutorial-evaluation';
import putUserSavedTutorial from './routes/put-user-saved-tutorial';
import loadScoOrganizationLearnersRoutes from './routes/sco-organization-learners/index';
import loadSupOrganizationLearnersRoutes from './routes/sup-organization-learners/index';
import updateCertificationCandidates from './routes/update-certification-candidates';
import loadUserRoutes from './routes/users/index';

export default function makeServer(config) {
Expand Down Expand Up @@ -108,4 +109,9 @@ function routes() {
this.get('/certification-courses/:id');

this.post('/feedbacks');

this.patch(
'/certification-candidates/:certificationCandidateId/validate-certification-instructions',
updateCertificationCandidates,
);
}
5 changes: 5 additions & 0 deletions mon-pix/mirage/routes/post-session-participation.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ export default function (schema, request) {
const firstName = params.data.attributes['first-name'];
const lastName = params.data.attributes['last-name'];
const birthdate = params.data.attributes['birthdate'];
let hasSeenCertificationInstructions = false;
if (!every([firstName, lastName, birthdate, sessionId])) {
return new Response(400);
}
if (lastName === 'hasSeenCertificationInstructions') {
hasSeenCertificationInstructions = true;
}
if (lastName === 'PasInscrite') {
return new Response(404);
}
Expand All @@ -32,5 +36,6 @@ export default function (schema, request) {
lastName: 'Bravo',
sessionId: 1,
birthdate: '1990-01-04',
hasSeenCertificationInstructions,
});
}
7 changes: 7 additions & 0 deletions mon-pix/mirage/routes/update-certification-candidates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function (schema, request) {
const certificationCandidateId = request.params.certificationCandidateId;
const certificationCandidate = schema.certificationCandidates.find(certificationCandidateId);
certificationCandidate.update({ hasSeenCertificationInstructions: true });

return certificationCandidate;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { visit } from '@1024pix/ember-testing-library';
import { currentURL } from '@ember/test-helpers';
import { click, currentURL } from '@ember/test-helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { setupApplicationTest } from 'ember-qunit';
import { module, test } from 'qunit';
Expand All @@ -21,7 +21,7 @@ module('Acceptance | Certifications | Information', function (hooks) {

module('when certification candidate participates in a V3 session', function () {
module('when toggle areV3InfoScreensEnabled is enabled', function () {
test('should display the certification information page', async function (assert) {
test('should display the certification instructions page', async function (assert) {
// given
server.create('feature-toggle', {
id: 0,
Expand Down Expand Up @@ -56,6 +56,88 @@ module('Acceptance | Certifications | Information', function (hooks) {
assert.dom(screen.getByRole('heading', { name: 'Bienvenue à la certification Pix', level: 2 })).exists();
assert.dom(screen.getByRole('button', { name: "Continuer vers l'écran suivant" })).exists();
});

module('when user validates instructions', function () {
test('should validate checkbox and redirect to the certification start page', async function (assert) {
// given
server.create('feature-toggle', {
id: 0,
areV3InfoScreensEnabled: true,
});
server.create('certification-candidate-subscription', {
id: 2,
sessionId: 123,
eligibleSubscription: null,
nonEligibleSubscription: null,
sessionVersion: 3,
});

await authenticateByEmail(user);

const screen = await visit('/certifications');

// when
await fillCertificationJoiner({
sessionId: '123',
firstName: 'toto',
lastName: 'titi',
dayOfBirth: '01',
monthOfBirth: '01',
yearOfBirth: '2000',
intl: this.intl,
});
for (let i = 0; i < 4; i++) {
await click(screen.getByRole('button', { name: "Continuer vers l'écran suivant" }));
}

await click(
screen.getByRole('checkbox', {
name: 'En cochant cette case, je reconnais avoir pris connaissance de ces règles et je m’engage à les respecter.',
}),
);
await click(screen.getByRole('button', { name: "Continuer vers la page d'entrée en certification" }));

// then
assert.strictEqual(currentURL(), '/certifications/candidat/2');
});
});

module('when user has already validated instructions', function () {
test('should redirect to the certification start page', async function (assert) {
// given
server.create('feature-toggle', {
id: 0,
areV3InfoScreensEnabled: true,
});
server.create('certification-candidate-subscription', {
id: 2,
sessionId: 123,
eligibleSubscription: null,
nonEligibleSubscription: null,
sessionVersion: 3,
});

const candidateLastName = 'hasSeenCertificationInstructions';

await authenticateByEmail(user);

await visit('/certifications');

// when
await fillCertificationJoiner({
sessionId: '123',
firstName: 'toto',
lastName: candidateLastName,
dayOfBirth: '01',
monthOfBirth: '01',
yearOfBirth: '2000',
intl: this.intl,
});

// then
assert.strictEqual(currentURL(), '/certifications/candidat/2');
});
});
});

module('when toggle areV3InfoScreensEnabled is not enabled', function () {
Expand Down
24 changes: 24 additions & 0 deletions mon-pix/tests/unit/adapters/certification-candidate_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,28 @@ module('Unit | Adapters | certification-candidate', function (hooks) {
assert.true(url.endsWith('/sessions/456/candidate-participation'));
});
});

module('#urlForUpdateRecord', function () {
module('when hasSeenCertificationInstructions option is true', function () {
AndreiaPena marked this conversation as resolved.
Show resolved Hide resolved
test('should redirect to session/id/certification-candidate/participation', async function (assert) {
// when
const options = { adapterOptions: { hasSeenCertificationInstructions: true } };
const url = await adapter.urlForUpdateRecord(456, 'certification-candidate', options);

// then
assert.true(url.endsWith('/certification-candidates/456/validate-certification-instructions'));
});
});

module('when hasSeenCertificationInstructions option is false', function () {
test('should build create url from certification-candidate id', async function (assert) {
// when
const options = { adapterOptions: {} };
const url = await adapter.urlForUpdateRecord(456, 'certification-candidate', options);

// then
assert.true(url.endsWith('/certification-candidates/456'));
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,29 @@ module('Unit | Component | certification-instruction | steps', function (hooks)

module('when pageId equal pageCount', function () {
module('when confirmation checkbox is checked', function () {
test('should redirect to certificartion starter', async function (assert) {
test('should redirect to certification starter', async function (assert) {
// given
const component = createGlimmerComponent('certification-instructions/steps');

component.pageId = 2;
component.pageCount = 2;
component.args = {
candidateId: 123,
};
component.isConfirmationCheckboxChecked = true;
const transitionToStub = sinon.stub();
const saveStub = sinon.stub();
saveStub.resolves();
component.router = {
transitionTo: transitionToStub,
};
component.args.candidate = {
save: saveStub,
id: 123,
};

// when
await component.nextStep();

// then
assert.ok(
transitionToStub.calledWith('authenticated.certifications.start', 123, {
queryParams: {
isConfirmationCheckboxChecked: true,
},
}),
);
assert.ok(transitionToStub.calledWith('authenticated.certifications.start', 123));
});
});
});
Expand Down
Loading