From 19ffe1422ee487d87fcf442feccf4324ed72c85b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9becca=20Kaci?= Date: Wed, 10 Jul 2024 17:13:06 +0200 Subject: [PATCH 1/9] feat(mon-pix): add new embed component Co-authored-by: Dimitri Lahaye --- .../app/components/module/element/embed.gjs | 17 +++++++++++ .../components/module/embed_test.gjs | 29 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 mon-pix/app/components/module/element/embed.gjs create mode 100644 mon-pix/tests/integration/components/module/embed_test.gjs diff --git a/mon-pix/app/components/module/element/embed.gjs b/mon-pix/app/components/module/element/embed.gjs new file mode 100644 index 00000000000..8a357ecc6e6 --- /dev/null +++ b/mon-pix/app/components/module/element/embed.gjs @@ -0,0 +1,17 @@ +import { htmlSafe } from '@ember/template'; +import Component from '@glimmer/component'; + +export default class ModulixEmbed extends Component { + embedHeight = this.args.embed.height; + + get embedDocumentHeightStyle() { + return htmlSafe(`height: ${this.embedHeight}px`); + } + + +} diff --git a/mon-pix/tests/integration/components/module/embed_test.gjs b/mon-pix/tests/integration/components/module/embed_test.gjs new file mode 100644 index 00000000000..6180e5ea7e1 --- /dev/null +++ b/mon-pix/tests/integration/components/module/embed_test.gjs @@ -0,0 +1,29 @@ +import { render } from '@1024pix/ember-testing-library'; +import ModulixEmbed from 'mon-pix/components/module/element/embed'; +import { module, test } from 'qunit'; + +import setupIntlRenderingTest from '../../../helpers/setup-intl-rendering'; + +module('Integration | Component | Module | Embed', function (hooks) { + setupIntlRenderingTest(hooks); + + test('should display an embed', async function (assert) { + // given + const embed = { + id: 'id', + title: 'title', + isCompletionRequired: false, + url: 'https://embed-pix.com', + height: 800, + }; + + // when + const screen = await render(); + + // then + assert.ok(screen); + const expectedIframe = screen.getByTitle(embed.title); + assert.strictEqual(expectedIframe.getAttribute('src'), embed.url); + assert.strictEqual(expectedIframe.style.getPropertyValue('height'), '800px'); + }); +}); From c54f3d587904a3441edd4dfaebdc72c97f5c14df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9becca=20Kaci?= Date: Wed, 10 Jul 2024 17:14:20 +0200 Subject: [PATCH 2/9] feat(mon-pix): handle embed in element component Co-authored-by: Dimitri Lahaye --- mon-pix/app/components/module/element.gjs | 3 +++ .../components/module/element_test.gjs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/mon-pix/app/components/module/element.gjs b/mon-pix/app/components/module/element.gjs index 5f7583872d4..23031f5e1cb 100644 --- a/mon-pix/app/components/module/element.gjs +++ b/mon-pix/app/components/module/element.gjs @@ -1,6 +1,7 @@ import { action } from '@ember/object'; import Component from '@glimmer/component'; import { eq } from 'ember-truth-helpers'; +import EmbedElement from 'mon-pix/components/module/element/embed'; import ImageElement from 'mon-pix/components/module/element/image'; import QcmElement from 'mon-pix/components/module/element/qcm'; import QcuElement from 'mon-pix/components/module/element/qcu'; @@ -21,6 +22,8 @@ export default class ModulixElement extends Component { {{else if (eq @element.type "video")}} + {{else if (eq @element.type "embed")}} + {{else if (eq @element.type "qcu")}} ); + + // then + assert.dom(screen.getByTitle(element.title)).exists(); + }); + test('should display an element with a qcu element', async function (assert) { // given const element = { From 9284b0143d6fdf72d6651fa049d4a1a8f4bfa5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9becca=20Kaci?= Date: Wed, 10 Jul 2024 17:14:44 +0200 Subject: [PATCH 3/9] feat(mon-pix): display embed into grain Co-authored-by: Dimitri Lahaye --- .../app/components/module/element/embed.gjs | 34 +++++++++++++++++-- mon-pix/app/components/module/grain.js | 2 +- mon-pix/app/styles/app.scss | 1 + .../app/styles/components/module/_embed.scss | 27 +++++++++++++++ .../components/module/embed_test.gjs | 26 +++++++++++++- mon-pix/translations/en.json | 6 ++++ mon-pix/translations/es.json | 6 ++++ mon-pix/translations/fr.json | 6 ++++ mon-pix/translations/nl.json | 6 ++++ 9 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 mon-pix/app/styles/components/module/_embed.scss diff --git a/mon-pix/app/components/module/element/embed.gjs b/mon-pix/app/components/module/element/embed.gjs index 8a357ecc6e6..9d98c440924 100644 --- a/mon-pix/app/components/module/element/embed.gjs +++ b/mon-pix/app/components/module/element/embed.gjs @@ -1,17 +1,45 @@ +import PixButton from '@1024pix/pix-ui/components/pix-button'; +import { action } from '@ember/object'; import { htmlSafe } from '@ember/template'; import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { t } from 'ember-intl'; export default class ModulixEmbed extends Component { + @tracked + isSimulatorLaunched = false; embedHeight = this.args.embed.height; - get embedDocumentHeightStyle() { + get heightStyle() { return htmlSafe(`height: ${this.embedHeight}px`); } + @action + startSimulator() { + this.isSimulatorLaunched = true; + } + } diff --git a/mon-pix/app/components/module/grain.js b/mon-pix/app/components/module/grain.js index dbd17f21318..d4166f2d23a 100644 --- a/mon-pix/app/components/module/grain.js +++ b/mon-pix/app/components/module/grain.js @@ -8,7 +8,7 @@ export default class ModuleGrain extends Component { grain = this.args.grain; - static AVAILABLE_ELEMENT_TYPES = ['text', 'image', 'video', 'qcu', 'qcm', 'qrocm']; + static AVAILABLE_ELEMENT_TYPES = ['text', 'image', 'video', 'embed', 'qcu', 'qcm', 'qrocm']; static AVAILABLE_GRAIN_TYPES = ['lesson', 'activity']; @tracked isStepperFinished = this.hasStepper === false; diff --git a/mon-pix/app/styles/app.scss b/mon-pix/app/styles/app.scss index 0cf9c09c30b..a6fa72bbec5 100644 --- a/mon-pix/app/styles/app.scss +++ b/mon-pix/app/styles/app.scss @@ -68,6 +68,7 @@ of an adaptative/mobile-first approach — refactoring is welcome here */ @import 'components/module/stepper'; @import 'components/module/image'; @import 'components/module/video'; +@import 'components/module/embed'; @import 'components/module/qcu'; @import 'components/module/qcm'; @import 'components/module/qrocm'; diff --git a/mon-pix/app/styles/components/module/_embed.scss b/mon-pix/app/styles/components/module/_embed.scss new file mode 100644 index 00000000000..2f07f5528b9 --- /dev/null +++ b/mon-pix/app/styles/components/module/_embed.scss @@ -0,0 +1,27 @@ +.element-embed { + position: relative; + + &__iframe { + display: block; + width: 100%; + overflow: hidden; + border: none; + + &--blurred { + filter: blur(5px); + } + } + + &__overlay { + position: absolute; + top: 0; + left: 0; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 55%); + } +} diff --git a/mon-pix/tests/integration/components/module/embed_test.gjs b/mon-pix/tests/integration/components/module/embed_test.gjs index 6180e5ea7e1..92bb77afad5 100644 --- a/mon-pix/tests/integration/components/module/embed_test.gjs +++ b/mon-pix/tests/integration/components/module/embed_test.gjs @@ -1,4 +1,4 @@ -import { render } from '@1024pix/ember-testing-library'; +import { clickByName, render } from '@1024pix/ember-testing-library'; import ModulixEmbed from 'mon-pix/components/module/element/embed'; import { module, test } from 'qunit'; @@ -25,5 +25,29 @@ module('Integration | Component | Module | Embed', function (hooks) { const expectedIframe = screen.getByTitle(embed.title); assert.strictEqual(expectedIframe.getAttribute('src'), embed.url); assert.strictEqual(expectedIframe.style.getPropertyValue('height'), '800px'); + assert + .dom(screen.getByRole('button', { name: this.intl.t('pages.modulix.buttons.embed.start.ariaLabel') })) + .exists(); + }); + + module('when user clicks on start button', function () { + test('should hide start button', async function (assert) { + // given + const embed = { + id: 'id', + title: 'title', + isCompletionRequired: false, + url: 'https://embed-pix.com', + height: 800, + }; + + // when + const screen = await render(); + + // then + const startButtonName = this.intl.t('pages.modulix.buttons.embed.start.ariaLabel'); + await clickByName(startButtonName); + assert.dom(screen.queryByRole('button', { name: startButtonName })).doesNotExist(); + }); }); }); diff --git a/mon-pix/translations/en.json b/mon-pix/translations/en.json index 31f50730bd2..f3f152e10cd 100644 --- a/mon-pix/translations/en.json +++ b/mon-pix/translations/en.json @@ -1311,6 +1311,12 @@ "alternativeText": "Show alternative text", "transcription": "Show transcription" }, + "embed": { + "start": { + "ariaLabel": "Start the simulator", + "name": "Start" + } + }, "grain": { "continue": "Continue", "skip": "Skip", diff --git a/mon-pix/translations/es.json b/mon-pix/translations/es.json index e8bf45c151e..45a092475cc 100644 --- a/mon-pix/translations/es.json +++ b/mon-pix/translations/es.json @@ -1311,6 +1311,12 @@ "alternativeText": "Mostrar el texto alternativo", "transcription": "Ver transcripción" }, + "embed": { + "start": { + "ariaLabel": "Iniciar el simulador", + "name": "Comenzar" + } + }, "grain": { "continue": "Continuar", "skip": "Ir a", diff --git a/mon-pix/translations/fr.json b/mon-pix/translations/fr.json index dedb1155d9e..74b7cb08259 100644 --- a/mon-pix/translations/fr.json +++ b/mon-pix/translations/fr.json @@ -1311,6 +1311,12 @@ "alternativeText": "Afficher l'alternative textuelle", "transcription": "Afficher la transcription" }, + "embed": { + "start": { + "ariaLabel": "Commencer le simulateur", + "name": "Commencer" + } + }, "grain": { "continue": "Continuer", "skip": "Passer", diff --git a/mon-pix/translations/nl.json b/mon-pix/translations/nl.json index 899be9f341d..b063e6dbd0b 100644 --- a/mon-pix/translations/nl.json +++ b/mon-pix/translations/nl.json @@ -1311,6 +1311,12 @@ "alternativeText": "Het tekstalternatief tonen", "transcription": "Transcriptie weergeven" }, + "embed": { + "start": { + "ariaLabel": "Start de simulator", + "name": "Beginnen" + } + }, "grain": { "continue": "Ga verder met", "skip": "Overslaan", From 543b8b7d5a15e3a54ecba8e165205643f16dcd7d Mon Sep 17 00:00:00 2001 From: dlahaye Date: Thu, 11 Jul 2024 16:59:43 +0200 Subject: [PATCH 4/9] feat(mon-pix): add reset button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rébecca Kaci --- .../app/components/module/element/embed.gjs | 26 ++++++++ .../app/styles/components/module/_embed.scss | 5 ++ .../components/module/embed_test.gjs | 49 ++++++++++++++- .../unit/components/module/embed_test.gjs | 60 +++++++++++++++++++ mon-pix/translations/en.json | 4 ++ mon-pix/translations/es.json | 4 ++ mon-pix/translations/fr.json | 4 ++ mon-pix/translations/nl.json | 4 ++ 8 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 mon-pix/tests/unit/components/module/embed_test.gjs diff --git a/mon-pix/app/components/module/element/embed.gjs b/mon-pix/app/components/module/element/embed.gjs index 9d98c440924..eeb6e56bf34 100644 --- a/mon-pix/app/components/module/element/embed.gjs +++ b/mon-pix/app/components/module/element/embed.gjs @@ -5,10 +5,24 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { t } from 'ember-intl'; +import didInsert from '../../../modifiers/modifier-did-insert'; + export default class ModulixEmbed extends Component { @tracked isSimulatorLaunched = false; embedHeight = this.args.embed.height; + iframe; + + @action + setIframeHtmlElement(htmlElement) { + this.iframe = htmlElement; + } + + @action + resetEmbed() { + this.iframe.setAttribute('src', this.args.embed.url); + this.iframe.focus(); + } get heightStyle() { return htmlSafe(`height: ${this.embedHeight}px`); @@ -17,6 +31,7 @@ export default class ModulixEmbed extends Component { @action startSimulator() { this.isSimulatorLaunched = true; + this.iframe.focus(); } } diff --git a/mon-pix/app/styles/components/module/_embed.scss b/mon-pix/app/styles/components/module/_embed.scss index 2f07f5528b9..90065680dca 100644 --- a/mon-pix/app/styles/components/module/_embed.scss +++ b/mon-pix/app/styles/components/module/_embed.scss @@ -24,4 +24,9 @@ height: 100%; background-color: rgb(0 0 0 / 55%); } + + &__reset { + display: flex; + justify-content: flex-end; + } } diff --git a/mon-pix/tests/integration/components/module/embed_test.gjs b/mon-pix/tests/integration/components/module/embed_test.gjs index 92bb77afad5..e0e59448c98 100644 --- a/mon-pix/tests/integration/components/module/embed_test.gjs +++ b/mon-pix/tests/integration/components/module/embed_test.gjs @@ -28,10 +28,13 @@ module('Integration | Component | Module | Embed', function (hooks) { assert .dom(screen.getByRole('button', { name: this.intl.t('pages.modulix.buttons.embed.start.ariaLabel') })) .exists(); + assert + .dom(screen.queryByRole('button', { name: this.intl.t('pages.modulix.buttons.embed.reset.ariaLabel') })) + .doesNotExist(); }); module('when user clicks on start button', function () { - test('should hide start button', async function (assert) { + test('should hide start button and display reset button', async function (assert) { // given const embed = { id: 'id', @@ -48,6 +51,50 @@ module('Integration | Component | Module | Embed', function (hooks) { const startButtonName = this.intl.t('pages.modulix.buttons.embed.start.ariaLabel'); await clickByName(startButtonName); assert.dom(screen.queryByRole('button', { name: startButtonName })).doesNotExist(); + assert + .dom(screen.getByRole('button', { name: this.intl.t('pages.modulix.buttons.embed.reset.ariaLabel') })) + .exists(); + }); + + test('should focus on the iframe', async function (assert) { + // given + const embed = { + id: 'id', + title: 'title', + isCompletionRequired: false, + url: 'https://embed-pix.com', + height: 800, + }; + const screen = await render(); + + // when + await clickByName(this.intl.t('pages.modulix.buttons.embed.start.ariaLabel')); + + // then + const iframe = screen.getByTitle(embed.title); + assert.strictEqual(document.activeElement, iframe); + }); + }); + + module('when user clicks on reset button', function () { + test('should focus on the iframe', async function (assert) { + // given + const embed = { + id: 'id', + title: 'title', + isCompletionRequired: false, + url: 'https://embed-pix.com', + height: 800, + }; + const screen = await render(); + + // when + await clickByName(this.intl.t('pages.modulix.buttons.embed.start.ariaLabel')); + await clickByName(this.intl.t('pages.modulix.buttons.embed.reset.ariaLabel')); + + // then + const iframe = screen.getByTitle(embed.title); + assert.strictEqual(document.activeElement, iframe); }); }); }); diff --git a/mon-pix/tests/unit/components/module/embed_test.gjs b/mon-pix/tests/unit/components/module/embed_test.gjs new file mode 100644 index 00000000000..4496c6dee12 --- /dev/null +++ b/mon-pix/tests/unit/components/module/embed_test.gjs @@ -0,0 +1,60 @@ +import { setupTest } from 'ember-qunit'; +import { module, test } from 'qunit'; +import sinon from 'sinon'; + +import createGlimmerComponent from '../../../helpers/create-glimmer-component'; + +module('Unit | Component | Module | Embed', function (hooks) { + setupTest(hooks); + + module('#setIframeHtmlElement', function () { + test('should set the iframe html element', async function (assert) { + // given + const embed = { + id: 'id', + title: 'title', + isCompletionRequired: false, + url: 'https://embed-pix.com', + height: 800, + }; + const component = createGlimmerComponent('module/element/embed', { + embed, + }); + const expectedComponentIFrame = Symbol('iframeHtmlElement'); + + // when + component.setIframeHtmlElement(expectedComponentIFrame); + + // then + assert.deepEqual(component.iframe, expectedComponentIFrame); + }); + }); + + module('#resetEmbed', function () { + test('should update the iframe src', async function (assert) { + // given + const embed = { + id: 'id', + title: 'title', + isCompletionRequired: false, + url: 'https://embed-pix.com', + height: 800, + }; + const component = createGlimmerComponent('module/element/embed', { + embed, + }); + component.iframe = { + setAttribute: sinon.stub(), + focus: sinon.stub(), + }; + + // when + component.resetEmbed(); + + // then + sinon.assert.calledWith(component.iframe.setAttribute, 'src', embed.url); + sinon.assert.called(component.iframe.focus); + assert.ok(true); + }); + }); +}); diff --git a/mon-pix/translations/en.json b/mon-pix/translations/en.json index f3f152e10cd..ab2d3eda99e 100644 --- a/mon-pix/translations/en.json +++ b/mon-pix/translations/en.json @@ -1312,6 +1312,10 @@ "transcription": "Show transcription" }, "embed": { + "reset": { + "ariaLabel": "Reset the simulator", + "name": "Reset" + }, "start": { "ariaLabel": "Start the simulator", "name": "Start" diff --git a/mon-pix/translations/es.json b/mon-pix/translations/es.json index 45a092475cc..d73458b4e14 100644 --- a/mon-pix/translations/es.json +++ b/mon-pix/translations/es.json @@ -1312,6 +1312,10 @@ "transcription": "Ver transcripción" }, "embed": { + "reset": { + "ariaLabel": "Reiniciar el simulador", + "name": "Reiniciar" + }, "start": { "ariaLabel": "Iniciar el simulador", "name": "Comenzar" diff --git a/mon-pix/translations/fr.json b/mon-pix/translations/fr.json index 74b7cb08259..ed5a7bf5a3f 100644 --- a/mon-pix/translations/fr.json +++ b/mon-pix/translations/fr.json @@ -1312,6 +1312,10 @@ "transcription": "Afficher la transcription" }, "embed": { + "reset": { + "ariaLabel": "Réinitialiser le simulateur", + "name": "Réinitialiser" + }, "start": { "ariaLabel": "Commencer le simulateur", "name": "Commencer" diff --git a/mon-pix/translations/nl.json b/mon-pix/translations/nl.json index b063e6dbd0b..cf492869f86 100644 --- a/mon-pix/translations/nl.json +++ b/mon-pix/translations/nl.json @@ -1312,6 +1312,10 @@ "transcription": "Transcriptie weergeven" }, "embed": { + "reset": { + "ariaLabel": "Reset de simulator", + "name": "Opnieuw instellen" + }, "start": { "ariaLabel": "Start de simulator", "name": "Beginnen" From 2ca590487e45a8308917452952c25937f2bda5c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9becca=20Kaci?= Date: Fri, 12 Jul 2024 12:15:41 +0200 Subject: [PATCH 5/9] refactor(api): remove default required from htmlSchema Co-authored-by: Dimitri Lahaye --- .../learning-content/validation/element/qcm.js | 2 +- .../learning-content/validation/element/qcu.js | 2 +- .../validation/element/qrocm.js | 2 +- .../learning-content/validation/utils.js | 18 ++++++++++-------- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qcm.js b/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qcm.js index 0f4e012aacc..fc290fa261b 100644 --- a/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qcm.js +++ b/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qcm.js @@ -5,7 +5,7 @@ import { htmlSchema, proposalIdSchema, uuidSchema } from '../utils.js'; const qcmElementSchema = Joi.object({ id: uuidSchema, type: Joi.string().valid('qcm').required(), - instruction: htmlSchema, + instruction: htmlSchema.required(), proposals: Joi.array() .items({ id: proposalIdSchema, diff --git a/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qcu.js b/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qcu.js index 3b8b25038fa..c11d8c2b590 100644 --- a/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qcu.js +++ b/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qcu.js @@ -5,7 +5,7 @@ import { htmlSchema, proposalIdSchema, uuidSchema } from '../utils.js'; const qcuElementSchema = Joi.object({ id: uuidSchema, type: Joi.string().valid('qcu').required(), - instruction: htmlSchema, + instruction: htmlSchema.required(), proposals: Joi.array() .items({ id: proposalIdSchema, diff --git a/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qrocm.js b/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qrocm.js index be9b54252b2..8ebbbb521c9 100644 --- a/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qrocm.js +++ b/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/qrocm.js @@ -45,7 +45,7 @@ const blockTextSchema = Joi.object({ const qrocmElementSchema = Joi.object({ id: uuidSchema, type: Joi.string().valid('qrocm').required(), - instruction: htmlSchema, + instruction: htmlSchema.required(), proposals: Joi.array() .items(Joi.alternatives().try(blockTextSchema, blockInputSchema, blockSelectSchema)) .required(), diff --git a/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/utils.js b/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/utils.js index 5bfbb1b03e8..6e8929517d4 100644 --- a/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/utils.js +++ b/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/utils.js @@ -6,15 +6,17 @@ const uuidSchema = Joi.string().guid({ version: 'uuidv4' }).required(); const proposalIdSchema = Joi.string().regex(/^\d+$/); const htmlValidate = new HtmlValidate(); -const htmlSchema = Joi.string() - .external(async (value, helpers) => { - const report = await htmlValidate.validateString(value); +const htmlSchema = Joi.string().external(async (value, helpers) => { + if (!value) { + return; + } - if (!report.valid) { - return helpers.message('htmlvalidationerror', { value: report }); - } - }) - .required(); + const report = await htmlValidate.validateString(value); + + if (!report.valid) { + return helpers.message('htmlvalidationerror', { value: report }); + } +}); const htmlNotAllowedSchema = Joi.string() .regex(/<.*?>/, { invert: true }) From 1f59c2bb037ce9d2fecaab75745b71759bee9249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9becca=20Kaci?= Date: Fri, 12 Jul 2024 12:20:01 +0200 Subject: [PATCH 6/9] feat(api): add optional embed instruction Co-authored-by: Dimitri Lahaye --- api/src/devcomp/domain/models/element/Embed.js | 3 ++- .../learning-content/modules/didacticiel-modulix.json | 3 ++- .../devcomp/infrastructure/factories/module-factory.js | 1 + .../devcomp/unit/domain/models/element/Embed_test.js | 10 +++++++++- .../learning-content/validation/element/embed.js | 3 ++- .../infrastructure/factories/module-factory_test.js | 1 + .../serializers/jsonapi/module-serializer_test.js | 10 +++++++++- 7 files changed, 26 insertions(+), 5 deletions(-) diff --git a/api/src/devcomp/domain/models/element/Embed.js b/api/src/devcomp/domain/models/element/Embed.js index 1abeaf2b23b..d8559f32862 100644 --- a/api/src/devcomp/domain/models/element/Embed.js +++ b/api/src/devcomp/domain/models/element/Embed.js @@ -2,7 +2,7 @@ import { assertNotNullOrUndefined } from '../../../../shared/domain/models/asser import { Element } from './Element.js'; class Embed extends Element { - constructor({ id, isCompletionRequired, title, url, height }) { + constructor({ id, isCompletionRequired, title, url, instruction, height }) { super({ id, type: 'embed' }); assertNotNullOrUndefined(isCompletionRequired, 'The isCompletionRequired attribute is required for an embed'); @@ -13,6 +13,7 @@ class Embed extends Element { this.isCompletionRequired = isCompletionRequired; this.title = title; this.url = url; + this.instruction = instruction; this.height = height; } } diff --git a/api/src/devcomp/infrastructure/datasources/learning-content/modules/didacticiel-modulix.json b/api/src/devcomp/infrastructure/datasources/learning-content/modules/didacticiel-modulix.json index 1fd14bacb34..7315136cd22 100644 --- a/api/src/devcomp/infrastructure/datasources/learning-content/modules/didacticiel-modulix.json +++ b/api/src/devcomp/infrastructure/datasources/learning-content/modules/didacticiel-modulix.json @@ -333,6 +333,7 @@ "isCompletionRequired": false, "title": "Application", "url": "https://epreuves.pix.fr/fake2.html", + "instruction": "

Parcourez ces photos trouvées sur le Web.

", "height": 640 } }, @@ -341,7 +342,7 @@ "element": { "id": "74d63ec2-f3e3-4ffe-9806-208d33588553", "type": "qcm", - "instruction": "

Parcourez ces photos trouvées sur le Web : lesquelles correspondent à des situations fictives ?

", + "instruction": "

Lesquelles correspondent à des situations fictives ?

", "proposals": [ { "id": "1", diff --git a/api/src/devcomp/infrastructure/factories/module-factory.js b/api/src/devcomp/infrastructure/factories/module-factory.js index 9242eafbbf9..cd4c5c74dd5 100644 --- a/api/src/devcomp/infrastructure/factories/module-factory.js +++ b/api/src/devcomp/infrastructure/factories/module-factory.js @@ -120,6 +120,7 @@ export class ModuleFactory { isCompletionRequired: element.isCompletionRequired, title: element.title, url: element.url, + instruction: element.instruction, height: element.height, }); } diff --git a/api/tests/devcomp/unit/domain/models/element/Embed_test.js b/api/tests/devcomp/unit/domain/models/element/Embed_test.js index f11f08f503b..4b243245c41 100644 --- a/api/tests/devcomp/unit/domain/models/element/Embed_test.js +++ b/api/tests/devcomp/unit/domain/models/element/Embed_test.js @@ -5,7 +5,14 @@ describe('Unit | Devcomp | Domain | Models | Element | Embed', function () { describe('#constructor', function () { it('should create an embed and keep attributes', function () { // given - const props = { id: 'id', isCompletionRequired: false, title: 'title', url: 'https://embed.com', height: 150 }; + const props = { + id: 'id', + isCompletionRequired: false, + title: 'title', + url: 'https://embed.com', + instruction: '

instruction

', + height: 150, + }; // when const embed = new Embed(props); @@ -16,6 +23,7 @@ describe('Unit | Devcomp | Domain | Models | Element | Embed', function () { expect(embed.isCompletionRequired).to.equal(false); expect(embed.title).to.equal('title'); expect(embed.url).to.equal('https://embed.com'); + expect(embed.instruction).to.equal('

instruction

'); expect(embed.height).to.equal(150); }); diff --git a/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/embed.js b/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/embed.js index 98bd7afbae9..8367690c11e 100644 --- a/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/embed.js +++ b/api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/element/embed.js @@ -1,6 +1,6 @@ import Joi from 'joi'; -import { htmlNotAllowedSchema, uuidSchema } from '../utils.js'; +import { htmlNotAllowedSchema, htmlSchema, uuidSchema } from '../utils.js'; const embedSchema = Joi.object({ id: uuidSchema, @@ -8,6 +8,7 @@ const embedSchema = Joi.object({ isCompletionRequired: Joi.boolean().valid(false).required(), title: htmlNotAllowedSchema.required(), url: Joi.string().uri().required(), + instruction: htmlSchema.optional(), height: Joi.number().min(0).required(), }).required(); diff --git a/api/tests/devcomp/unit/infrastructure/factories/module-factory_test.js b/api/tests/devcomp/unit/infrastructure/factories/module-factory_test.js index 122c26fc3f0..18ed6f44865 100644 --- a/api/tests/devcomp/unit/infrastructure/factories/module-factory_test.js +++ b/api/tests/devcomp/unit/infrastructure/factories/module-factory_test.js @@ -865,6 +865,7 @@ describe('Unit | Devcomp | Infrastructure | Factories | Module ', function () { isCompletionRequired: false, title: "Simulateur d'adresse mail", url: 'https://embed.fr', + instruction: '

Parcourez ces photos trouvées sur le Web.

', height: 150, }, ], diff --git a/api/tests/devcomp/unit/infrastructure/serializers/jsonapi/module-serializer_test.js b/api/tests/devcomp/unit/infrastructure/serializers/jsonapi/module-serializer_test.js index 31db90af486..d82d1fc608d 100644 --- a/api/tests/devcomp/unit/infrastructure/serializers/jsonapi/module-serializer_test.js +++ b/api/tests/devcomp/unit/infrastructure/serializers/jsonapi/module-serializer_test.js @@ -214,7 +214,14 @@ function getComponents() { element: new Image({ id: '3', url: 'url', alt: 'alt', alternativeText: 'alternativeText' }), }), new ComponentElement({ - element: new Embed({ id: '3', url: 'url', height: 400, title: 'title', isCompletionRequired: false }), + element: new Embed({ + id: '3', + url: 'url', + height: 400, + title: 'title', + instruction: '

instruction

', + isCompletionRequired: false, + }), }), new ComponentElement({ element: new Video({ @@ -357,6 +364,7 @@ function getAttributesComponents() { isCompletionRequired: false, type: 'embed', url: 'url', + instruction: '

instruction

', }, }, { From 4af5452b6f27807014c544bc52d43e48603698f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9becca=20Kaci?= Date: Fri, 12 Jul 2024 16:01:43 +0200 Subject: [PATCH 7/9] feat(mon-pix): display embed instruction when given Co-authored-by: Dimitri Lahaye --- .../app/components/module/element/embed.gjs | 50 +++++++++++-------- .../app/styles/components/module/_embed.scss | 21 ++++++-- .../components/module/embed_test.gjs | 23 ++++++++- 3 files changed, 68 insertions(+), 26 deletions(-) diff --git a/mon-pix/app/components/module/element/embed.gjs b/mon-pix/app/components/module/element/embed.gjs index eeb6e56bf34..19f9a6d9761 100644 --- a/mon-pix/app/components/module/element/embed.gjs +++ b/mon-pix/app/components/module/element/embed.gjs @@ -1,10 +1,10 @@ import PixButton from '@1024pix/pix-ui/components/pix-button'; import { action } from '@ember/object'; -import { htmlSafe } from '@ember/template'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { t } from 'ember-intl'; +import { htmlUnsafe } from '../../../helpers/html-unsafe'; import didInsert from '../../../modifiers/modifier-did-insert'; export default class ModulixEmbed extends Component { @@ -25,7 +25,7 @@ export default class ModulixEmbed extends Component { } get heightStyle() { - return htmlSafe(`height: ${this.embedHeight}px`); + return htmlUnsafe(`height: ${this.embedHeight}px`); } @action @@ -36,26 +36,36 @@ export default class ModulixEmbed extends Component {