Skip to content

Commit 4025134

Browse files
committedMar 24, 2025
feat(api): allow custom elements
1 parent cbd87f5 commit 4025134

File tree

5 files changed

+146
-0
lines changed

5 files changed

+146
-0
lines changed
 
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { assertNotNullOrUndefined } from '../../../../shared/domain/models/asserts.js';
2+
import { Element } from './Element.js';
3+
4+
class CustomElement extends Element {
5+
/**
6+
*
7+
* @param{string} id
8+
* @param{string} tagName
9+
* @param{string} props
10+
*/
11+
constructor({ id, tagName, props } = {}) {
12+
super({ id, type: 'custom' });
13+
assertNotNullOrUndefined(tagName, 'The tagName is required for an CustomElement element');
14+
assertNotNullOrUndefined(props, 'The props are required for an CustomElement element');
15+
this.tagName = tagName;
16+
this.props = props;
17+
}
18+
}
19+
20+
export { CustomElement };

Diff for: ‎api/src/devcomp/infrastructure/factories/module-factory.js

+11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { BlockText } from '../../domain/models/block/BlockText.js';
77
import { ComponentElement } from '../../domain/models/component/ComponentElement.js';
88
import { ComponentStepper } from '../../domain/models/component/ComponentStepper.js';
99
import { Step } from '../../domain/models/component/Step.js';
10+
import { CustomElement } from '../../domain/models/element/CustomElement.js';
1011
import { Download } from '../../domain/models/element/Download.js';
1112
import { Embed } from '../../domain/models/element/Embed.js';
1213
import { Expand } from '../../domain/models/element/Expand.js';
@@ -89,6 +90,8 @@ export class ModuleFactory {
8990

9091
static #buildElement(element) {
9192
switch (element.type) {
93+
case 'custom':
94+
return ModuleFactory.#buildCustom(element);
9295
case 'download':
9396
return ModuleFactory.#buildDownload(element);
9497
case 'embed':
@@ -120,6 +123,14 @@ export class ModuleFactory {
120123
}
121124
}
122125

126+
static #buildCustom(element) {
127+
return new CustomElement({
128+
id: element.id,
129+
tagName: element.tagName,
130+
props: element.props,
131+
});
132+
}
133+
123134
static #buildDownload(element) {
124135
return new Download({
125136
id: element.id,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { CustomElement } from '../../../../../../src/devcomp/domain/models/element/CustomElement.js';
2+
import { DomainError } from '../../../../../../src/shared/domain/errors.js';
3+
import { catchErrSync, expect } from '../../../../../test-helper.js';
4+
5+
describe('Unit | Devcomp | Domain | Models | Element | CustomElement', function () {
6+
describe('#constructor', function () {
7+
it('should create a valid CustomElement object', function () {
8+
// given
9+
const attributes = {
10+
id: '5ce0ddf1-8620-43b5-9e43-cd9b2ffaca17',
11+
tagName: 'qcu-image',
12+
props: {
13+
name: "Liste d'applications",
14+
maxChoicesPerLine: 3,
15+
imageChoicesSize: 'icon',
16+
choices: [
17+
{
18+
name: 'Google',
19+
image: {
20+
width: 534,
21+
height: 544,
22+
loading: 'lazy',
23+
decoding: 'async',
24+
src: 'https://epreuves.pix.fr/_astro/Google.B1bcY5Go_1BynY8.svg',
25+
},
26+
},
27+
{
28+
name: 'LibreOffice Writer',
29+
image: {
30+
width: 205,
31+
height: 246,
32+
loading: 'lazy',
33+
decoding: 'async',
34+
src: 'https://epreuves.pix.fr/_astro/writer.3bR8N2DK_Z1iWuJ9.webp',
35+
},
36+
},
37+
{
38+
name: 'Explorateur',
39+
image: {
40+
width: 128,
41+
height: 128,
42+
loading: 'lazy',
43+
decoding: 'async',
44+
src: 'https://epreuves.pix.fr/_astro/windows-file-explorer.CnF8MYwI_23driA.webp',
45+
},
46+
},
47+
{
48+
name: 'Geogebra',
49+
image: {
50+
width: 640,
51+
height: 640,
52+
loading: 'lazy',
53+
decoding: 'async',
54+
src: 'https://epreuves.pix.fr/_astro/geogebra.CZH9VYqc_19v4nj.webp',
55+
},
56+
},
57+
],
58+
},
59+
};
60+
61+
// when
62+
const result = new CustomElement(attributes);
63+
64+
// then
65+
expect(result.id).to.equal(attributes.id);
66+
expect(result.tagName).to.equal(attributes.tagName);
67+
expect(result.props).to.deep.equal(attributes.props);
68+
expect(result.type).to.equal('custom');
69+
});
70+
});
71+
72+
describe('A CustomElement without a tagName', function () {
73+
it('should throw an error', function () {
74+
const attributes = {
75+
id: '5ce0ddf1-8620-43b5-9e43-cd9b2ffaca17',
76+
};
77+
// when
78+
const error = catchErrSync(() => new CustomElement(attributes))();
79+
80+
// then
81+
expect(error).to.be.instanceOf(DomainError);
82+
expect(error.message).to.equal('The tagName is required for an CustomElement element');
83+
});
84+
});
85+
86+
describe('A CustomElement without props', function () {
87+
it('should throw an error', function () {
88+
const attributes = {
89+
id: '5ce0ddf1-8620-43b5-9e43-cd9b2ffaca17',
90+
tagName: 'qcu-image',
91+
};
92+
// when
93+
const error = catchErrSync(() => new CustomElement(attributes))();
94+
95+
// then
96+
expect(error).to.be.instanceOf(DomainError);
97+
expect(error.message).to.equal('The props are required for an CustomElement element');
98+
});
99+
});
100+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Joi from 'joi';
2+
3+
import { uuidSchema } from '../utils.js';
4+
5+
const customElementSchema = Joi.object({
6+
id: uuidSchema,
7+
type: Joi.string().valid('custom').required(),
8+
tagName: Joi.string().required(),
9+
props: Joi.object().required(),
10+
}).required();
11+
12+
export { customElementSchema };

Diff for: ‎api/tests/devcomp/unit/infrastructure/datasources/learning-content/validation/module-schema.js

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Joi from 'joi';
22

3+
import { customElementSchema } from './element/custom-element-schema.js';
34
import { downloadElementSchema } from './element/download-schema.js';
45
import { embedElementSchema } from './element/embed-schema.js';
56
import { expandElementSchema } from './element/expand-schema.js';
@@ -29,6 +30,7 @@ const moduleDetailsSchema = Joi.object({
2930

3031
const elementSchema = Joi.alternatives().conditional('.type', {
3132
switch: [
33+
{ is: 'custom', then: customElementSchema },
3234
{ is: 'download', then: downloadElementSchema },
3335
{ is: 'embed', then: embedElementSchema },
3436
{ is: 'expand', then: expandElementSchema },
@@ -45,6 +47,7 @@ const elementSchema = Joi.alternatives().conditional('.type', {
4547

4648
const stepperElementSchema = Joi.alternatives().conditional('.type', {
4749
switch: [
50+
{ is: 'custom', then: customElementSchema },
4851
{ is: 'download', then: downloadElementSchema },
4952
{ is: 'expand', then: expandElementSchema },
5053
{ is: 'image', then: imageElementSchema },

0 commit comments

Comments
 (0)
Failed to load comments.