refactor(backend): Streamlining van de testdata voor leerpaden en leerobjecten + integratie in seed

Hierbij ook testdata functionaliteit toegevoegd om makkelijk nieuwe leerpaden aan te maken.
This commit is contained in:
Gerald Schmittinger 2025-04-15 23:43:30 +02:00
parent 4092f1f617
commit 202cf4e33c
32 changed files with 691 additions and 493 deletions

View file

@ -1,6 +1,10 @@
import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
import { LearningPath } from '../../entities/content/learning-path.entity.js'; import { LearningPath } from '../../entities/content/learning-path.entity.js';
import { Language } from '@dwengo-1/common/util/language'; import { Language } from '@dwengo-1/common/util/language';
import {LearningPathNode} from "../../entities/content/learning-path-node.entity";
import {RequiredEntityData} from "@mikro-orm/core";
import {LearningPathTransition} from "../../entities/content/learning-path-transition.entity";
import {EntityAlreadyExistsException} from "../../exceptions/entity-already-exists-exception";
export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { export class LearningPathRepository extends DwengoEntityRepository<LearningPath> {
public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> {
@ -23,4 +27,33 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath>
populate: ['nodes', 'nodes.transitions'], populate: ['nodes', 'nodes.transitions'],
}); });
} }
public createNode(
nodeData: RequiredEntityData<LearningPathNode, never, false>
): LearningPathNode {
return this.em.create(LearningPathNode, nodeData);
}
public createTransition(
transitionData: RequiredEntityData<LearningPathTransition, never, false>
): LearningPathTransition {
return this.em.create(LearningPathTransition, transitionData)
}
public async saveLearningPathNodesAndTransitions(
path: LearningPath,
nodes: LearningPathNode[],
transitions: LearningPathTransition[],
options?: {preventOverwrite?: boolean}
): Promise<void> {
if (options?.preventOverwrite && (await this.findOne(path))) {
throw new EntityAlreadyExistsException(
"A learning path with this hruid/language combination already exists."
);
}
const em = this.getEntityManager();
await em.persistAndFlush(path);
await Promise.all(nodes.map(it => em.persistAndFlush(it)));
await Promise.all(transitions.map(it => em.persistAndFlush(it)));
}
} }

View file

@ -1,4 +1,4 @@
import { Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property, Rel } from '@mikro-orm/core'; import {Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property, Rel} from '@mikro-orm/core';
import { LearningPath } from './learning-path.entity.js'; import { LearningPath } from './learning-path.entity.js';
import { LearningPathTransition } from './learning-path-transition.entity.js'; import { LearningPathTransition } from './learning-path-transition.entity.js';
import { Language } from '@dwengo-1/common/util/language'; import { Language } from '@dwengo-1/common/util/language';
@ -27,7 +27,7 @@ export class LearningPathNode {
startNode!: boolean; startNode!: boolean;
@OneToMany({ entity: () => LearningPathTransition, mappedBy: 'node' }) @OneToMany({ entity: () => LearningPathTransition, mappedBy: 'node' })
transitions: LearningPathTransition[] = []; transitions: Collection<LearningPathTransition> = new Collection<LearningPathTransition>(this);
@Property({ length: 3 }) @Property({ length: 3 })
createdAt: Date = new Date(); createdAt: Date = new Date();

View file

@ -1,4 +1,4 @@
import { Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; import {Collection, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property} from '@mikro-orm/core';
import { Teacher } from '../users/teacher.entity.js'; import { Teacher } from '../users/teacher.entity.js';
import { LearningPathRepository } from '../../data/content/learning-path-repository.js'; import { LearningPathRepository } from '../../data/content/learning-path-repository.js';
import { LearningPathNode } from './learning-path-node.entity.js'; import { LearningPathNode } from './learning-path-node.entity.js';
@ -25,5 +25,5 @@ export class LearningPath {
image: Buffer | null = null; image: Buffer | null = null;
@OneToMany({ entity: () => LearningPathNode, mappedBy: 'learningPath' }) @OneToMany({ entity: () => LearningPathNode, mappedBy: 'learningPath' })
nodes: LearningPathNode[] = []; nodes: Collection<LearningPathNode> = new Collection<LearningPathNode>(this);
} }

View file

@ -14,13 +14,14 @@ import {
} from '@dwengo-1/common/interfaces/learning-content'; } from '@dwengo-1/common/interfaces/learning-content';
import { Language } from '@dwengo-1/common/util/language'; import { Language } from '@dwengo-1/common/util/language';
import {Group} from "../../entities/assignments/group.entity"; import {Group} from "../../entities/assignments/group.entity";
import {Collection} from "@mikro-orm/core";
/** /**
* Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its * Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its
* corresponding learning object. * corresponding learning object.
* @param nodes The nodes to find the learning object for. * @param nodes The nodes to find the learning object for.
*/ */
async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise<Map<LearningPathNode, FilteredLearningObject>> { async function getLearningObjectsForNodes(nodes: Collection<LearningPathNode>): Promise<Map<LearningPathNode, FilteredLearningObject>> {
// Fetching the corresponding learning object for each of the nodes and creating a map that maps each node to // Fetching the corresponding learning object for each of the nodes and creating a map that maps each node to
// Its corresponding learning object. // Its corresponding learning object.
const nullableNodesToLearningObjects = new Map<LearningPathNode, FilteredLearningObject | null>( const nullableNodesToLearningObjects = new Map<LearningPathNode, FilteredLearningObject | null>(
@ -208,7 +209,7 @@ const databaseLearningPathProvider: LearningPathProvider = {
const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language); const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language);
return await Promise.all(searchResults.map(async (result, index) => convertLearningPath(result, index, personalizedFor))); return await Promise.all(searchResults.map(async (result, index) => convertLearningPath(result, index, personalizedFor)));
}, }
}; };
export default databaseLearningPathProvider; export default databaseLearningPathProvider;

View file

@ -1,13 +1,79 @@
import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js';
import databaseLearningPathProvider from './database-learning-path-provider.js'; import databaseLearningPathProvider from './database-learning-path-provider.js';
import { envVars, getEnvVar } from '../../util/envVars.js'; import { envVars, getEnvVar } from '../../util/envVars.js';
import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; import {LearningObjectNode, LearningPath, LearningPathResponse} from '@dwengo-1/common/interfaces/learning-content';
import { Language } from '@dwengo-1/common/util/language'; import { Language } from '@dwengo-1/common/util/language';
import {Group} from "../../entities/assignments/group.entity"; import {Group} from "../../entities/assignments/group.entity";
import {LearningPath as LearningPathEntity} from "../../entities/content/learning-path.entity";
import {getLearningPathRepository} from "../../data/repositories";
import {LearningPathNode} from "../../entities/content/learning-path-node.entity";
import {LearningPathTransition} from "../../entities/content/learning-path-transition.entity";
import {base64ToArrayBuffer} from "../../util/base64-buffer-conversion";
import {TeacherDTO} from "@dwengo-1/common/interfaces/teacher";
import {mapToTeacher} from "../../interfaces/teacher";
import {Collection} from "@mikro-orm/core";
const userContentPrefix = getEnvVar(envVars.UserContentPrefix); const userContentPrefix = getEnvVar(envVars.UserContentPrefix);
const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]; const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider];
export function mapToLearningPath(
dto: LearningPath, adminsDto: TeacherDTO[]
): LearningPathEntity {
const admins = adminsDto.map(admin => mapToTeacher(admin));
const repo = getLearningPathRepository();
const path = repo.create({
hruid: dto.hruid,
language: dto.language as Language,
description: dto.description,
title: dto.title,
admins,
image: dto.image ? Buffer.from(base64ToArrayBuffer(dto.image)) : null
});
const nodes = dto.nodes.map((nodeDto: LearningObjectNode, i: number) =>
repo.createNode({
learningPath: path,
learningObjectHruid: nodeDto.learningobject_hruid,
language: nodeDto.language,
version: nodeDto.version,
startNode: nodeDto.start_node ?? false,
nodeNumber: i,
createdAt: new Date(),
updatedAt: new Date()
})
);
dto.nodes.forEach(nodeDto => {
const fromNode = nodes.find(it =>
it.learningObjectHruid === nodeDto.learningobject_hruid
&& it.language === nodeDto.language
&& it.version === nodeDto.version
)!;
const transitions = nodeDto.transitions.map((transDto, i) => {
const toNode = nodes.find(it =>
it.learningObjectHruid === transDto.next.hruid
&& it.language === transDto.next.language
&& it.version === transDto.next.version
);
if (toNode) {
return repo.createTransition({
transitionNumber: i,
node: fromNode,
next: toNode,
condition: transDto.condition ?? "true"
});
} else {
return undefined;
}
}).filter(it => it).map(it => it!);
fromNode.transitions = new Collection<LearningPathTransition>(transitions);
});
path.nodes = new Collection<LearningPathNode>(nodes);
return path;
}
/** /**
* Service providing access to data about learning paths from the appropriate data source (database or Dwengo-api) * Service providing access to data about learning paths from the appropriate data source (database or Dwengo-api)
*/ */
@ -54,6 +120,17 @@ const learningPathService = {
); );
return providerResponses.flat(); return providerResponses.flat();
}, },
/**
* Add a new learning path to the database.
* @param dto Learning path DTO from which the learning path will be created.
* @param admins Teachers who should become an admin of the learning path.
*/
async createNewLearningPath(dto: LearningPath, admins: TeacherDTO[]): Promise<void> {
const repo = getLearningPathRepository();
const path = mapToLearningPath(dto, admins);
await repo.save(path, {preventOverwrite: true})
}
}; };
export default learningPathService; export default learningPathService;

View file

@ -0,0 +1,12 @@
/**
* Convert a Base64-encoded string into a buffer with the same data.
* @param base64 The Base64 encoded string.
*/
export function base64ToArrayBuffer(base64: string) {
var binaryString = atob(base64);
var bytes = new Uint8Array(binaryString.length);
for (var i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}

View file

@ -2,48 +2,36 @@ import { beforeAll, describe, it, expect } from 'vitest';
import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.js'; import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.js';
import { setupTestApp } from '../../setup-tests.js'; import { setupTestApp } from '../../setup-tests.js';
import { getLearningObjectRepository } from '../../../src/data/repositories.js'; import { getLearningObjectRepository } from '../../../src/data/repositories.js';
import example from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.js';
import { LearningObject } from '../../../src/entities/content/learning-object.entity.js'; import { LearningObject } from '../../../src/entities/content/learning-object.entity.js';
import { expectToBeCorrectEntity } from '../../test-utils/expectations.js'; import { expectToBeCorrectEntity } from '../../test-utils/expectations.js';
import {
testLearningObject01,
testLearningObject02,
testLearningObject03
} from "../../test_assets/content/learning-objects.testdata";
describe('LearningObjectRepository', () => { describe('LearningObjectRepository', () => {
let learningObjectRepository: LearningObjectRepository; let learningObjectRepository: LearningObjectRepository;
let exampleLearningObject: LearningObject;
beforeAll(async () => { beforeAll(async () => {
await setupTestApp(); await setupTestApp();
learningObjectRepository = getLearningObjectRepository(); learningObjectRepository = getLearningObjectRepository();
}); });
it('should be able to add a learning object to it without an error', async () => { it('should return a learning object when queried by id', async () => {
exampleLearningObject = example.createLearningObject();
await learningObjectRepository.insert(exampleLearningObject);
});
it('should return the learning object when queried by id', async () => {
const result = await learningObjectRepository.findByIdentifier({ const result = await learningObjectRepository.findByIdentifier({
hruid: exampleLearningObject.hruid, hruid: testLearningObject01.hruid,
language: exampleLearningObject.language, language: testLearningObject02.language,
version: exampleLearningObject.version, version: testLearningObject03.version,
}); });
expect(result).toBeInstanceOf(LearningObject); expect(result).toBeInstanceOf(LearningObject);
expectToBeCorrectEntity( expectToBeCorrectEntity(result!, testLearningObject01);
{
name: 'actual',
entity: result!,
},
{
name: 'expected',
entity: exampleLearningObject,
}
);
}); });
it('should return null when non-existing version is queried', async () => { it('should return null when non-existing version is queried', async () => {
const result = await learningObjectRepository.findByIdentifier({ const result = await learningObjectRepository.findByIdentifier({
hruid: exampleLearningObject.hruid, hruid: testLearningObject01.hruid,
language: exampleLearningObject.language, language: testLearningObject01.language,
version: 100, version: 100,
}); });
expect(result).toBe(null); expect(result).toBe(null);
@ -52,21 +40,27 @@ describe('LearningObjectRepository', () => {
let newerExample: LearningObject; let newerExample: LearningObject;
it('should allow a learning object with the same id except a different version to be added', async () => { it('should allow a learning object with the same id except a different version to be added', async () => {
newerExample = example.createLearningObject(); let testLearningObject01Newer = structuredClone(testLearningObject01);
newerExample.version = 10; testLearningObject01Newer.version = 10;
newerExample.title += ' (nieuw)'; testLearningObject01Newer.title += " (nieuw)";
testLearningObject01Newer.content = Buffer.from("This is the new content.");
newerExample = learningObjectRepository.create(testLearningObject01Newer);
await learningObjectRepository.save(newerExample); await learningObjectRepository.save(newerExample);
}); });
it('should return the newest version of the learning object when queried by only hruid and language', async () => { it('should return the newest version of the learning object when queried by only hruid and language', async () => {
const result = await learningObjectRepository.findLatestByHruidAndLanguage(newerExample.hruid, newerExample.language); const result = await learningObjectRepository.findLatestByHruidAndLanguage(
newerExample.hruid, newerExample.language
);
expect(result).toBeInstanceOf(LearningObject); expect(result).toBeInstanceOf(LearningObject);
expect(result?.version).toBe(10); expect(result?.version).toBe(10);
expect(result?.title).toContain('(nieuw)'); expect(result?.title).toContain('(nieuw)');
}); });
it('should return null when queried by non-existing hruid or language', async () => { it('should return null when queried by non-existing hruid or language', async () => {
const result = await learningObjectRepository.findLatestByHruidAndLanguage('something_that_does_not_exist', exampleLearningObject.language); const result = await learningObjectRepository.findLatestByHruidAndLanguage(
'something_that_does_not_exist', testLearningObject01.language
);
expect(result).toBe(null); expect(result).toBe(null);
}); });
}); });

View file

@ -1,10 +0,0 @@
import { LearningObjectExample } from './learning-object-example';
import { LearningObject } from '../../../src/entities/content/learning-object.entity';
export function createExampleLearningObjectWithAttachments(example: LearningObjectExample): LearningObject {
const learningObject = example.createLearningObject();
for (const creationFn of Object.values(example.createAttachment)) {
learningObject.attachments.push(creationFn(learningObject));
}
return learningObject;
}

View file

@ -1,8 +0,0 @@
import { LearningObject } from '../../../src/entities/content/learning-object.entity';
import { Attachment } from '../../../src/entities/content/attachment.entity';
interface LearningObjectExample {
createLearningObject: () => LearningObject;
createAttachment: Record<string, (owner: LearningObject) => Attachment>;
getHTMLRendering: () => string;
}

View file

@ -1,2 +0,0 @@
::MC basic::
How are you? {}

View file

@ -1,3 +0,0 @@
interface LearningPathExample {
createLearningPath: () => LearningPath;
}

View file

@ -1,36 +0,0 @@
import { Language } from '@dwengo-1/common/util/language';
import { LearningPathTransition } from '../../../src/entities/content/learning-path-transition.entity';
import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity';
import { LearningPath } from '../../../src/entities/content/learning-path.entity';
export function createLearningPathTransition(
node: LearningPathNode,
transitionNumber: number,
condition: string | null,
to: LearningPathNode
): LearningPathTransition {
const trans = new LearningPathTransition();
trans.node = node;
trans.transitionNumber = transitionNumber;
trans.condition = condition || 'true';
trans.next = to;
return trans;
}
export function createLearningPathNode(
learningPath: LearningPath,
nodeNumber: number,
learningObjectHruid: string,
version: number,
language: Language,
startNode: boolean
): LearningPathNode {
const node = new LearningPathNode();
node.learningPath = learningPath;
node.nodeNumber = nodeNumber;
node.learningObjectHruid = learningObjectHruid;
node.version = version;
node.language = language;
node.startNode = startNode;
return node;
}

View file

@ -1,30 +0,0 @@
import { LearningPath } from '../../../src/entities/content/learning-path.entity';
import { Language } from '@dwengo-1/common/util/language';
import { envVars, getEnvVar } from '../../../src/util/envVars';
import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils';
import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity';
function createNodes(learningPath: LearningPath): LearningPathNode[] {
const nodes = [
createLearningPathNode(learningPath, 0, 'u_pn_werkingnotebooks', 3, Language.Dutch, true),
createLearningPathNode(learningPath, 1, 'pn_werkingnotebooks2', 3, Language.Dutch, false),
createLearningPathNode(learningPath, 2, 'pn_werkingnotebooks3', 3, Language.Dutch, false),
];
nodes[0].transitions.push(createLearningPathTransition(nodes[0], 0, 'true', nodes[1]));
nodes[1].transitions.push(createLearningPathTransition(nodes[1], 0, 'true', nodes[2]));
return nodes;
}
const example: LearningPathExample = {
createLearningPath: () => {
const path = new LearningPath();
path.language = Language.Dutch;
path.hruid = `${getEnvVar(envVars.UserContentPrefix)}pn_werking`;
path.title = 'Werken met notebooks';
path.description = 'Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?';
path.nodes = createNodes(path);
return path;
},
};
export default example;

View file

@ -1,80 +0,0 @@
import { LearningPath } from '../../../src/entities/content/learning-path.entity';
import { Language } from '@dwengo-1/common/util/language';
import testMultipleChoiceExample from '../learning-objects/test-multiple-choice/test-multiple-choice-example';
import { dummyLearningObject } from '../learning-objects/dummy/dummy-learning-object-example';
import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils';
import { LearningObject } from '../../../src/entities/content/learning-object.entity';
import { envVars, getEnvVar } from '../../../src/util/envVars';
export interface ConditionTestLearningPathAndLearningObjects {
branchingObject: LearningObject;
extraExerciseObject: LearningObject;
finalObject: LearningObject;
learningPath: LearningPath;
}
export function createConditionTestLearningPathAndLearningObjects(): ConditionTestLearningPathAndLearningObjects {
const learningPath = new LearningPath();
learningPath.hruid = `${getEnvVar(envVars.UserContentPrefix)}test_conditions`;
learningPath.language = Language.English;
learningPath.title = 'Example learning path with conditional transitions';
learningPath.description = 'This learning path was made for the purpose of testing conditional transitions';
const branchingLearningObject = testMultipleChoiceExample.createLearningObject();
const extraExerciseLearningObject = dummyLearningObject(
'test_extra_exercise',
Language.English,
'Extra exercise (for students with difficulties)'
).createLearningObject();
const finalLearningObject = dummyLearningObject(
'test_final_learning_object',
Language.English,
'Final exercise (for everyone)'
).createLearningObject();
const branchingNode = createLearningPathNode(
learningPath,
0,
branchingLearningObject.hruid,
branchingLearningObject.version,
branchingLearningObject.language,
true
);
const extraExerciseNode = createLearningPathNode(
learningPath,
1,
extraExerciseLearningObject.hruid,
extraExerciseLearningObject.version,
extraExerciseLearningObject.language,
false
);
const finalNode = createLearningPathNode(
learningPath,
2,
finalLearningObject.hruid,
finalLearningObject.version,
finalLearningObject.language,
false
);
const transitionToExtraExercise = createLearningPathTransition(
branchingNode,
0,
'$[?(@[0] == 0)]', // The answer to the first question was the first one, which says that it is difficult for the student to follow along.
extraExerciseNode
);
const directTransitionToFinal = createLearningPathTransition(branchingNode, 1, '$[?(@[0] == 1)]', finalNode);
const transitionExtraExerciseToFinal = createLearningPathTransition(extraExerciseNode, 0, 'true', finalNode);
branchingNode.transitions = [transitionToExtraExercise, directTransitionToFinal];
extraExerciseNode.transitions = [transitionExtraExerciseToFinal];
learningPath.nodes = [branchingNode, extraExerciseNode, finalNode];
return {
branchingObject: branchingLearningObject,
finalObject: finalLearningObject,
extraExerciseObject: extraExerciseLearningObject,
learningPath: learningPath,
};
}

View file

@ -11,52 +11,45 @@ const IGNORE_PROPERTIES = ['parent'];
* Checks if the actual entity from the database conforms to the entity that was added previously. * Checks if the actual entity from the database conforms to the entity that was added previously.
* @param actual The actual entity retrieved from the database * @param actual The actual entity retrieved from the database
* @param expected The (previously added) entity we would expect to retrieve * @param expected The (previously added) entity we would expect to retrieve
* @param propertyPrefix Prefix to append to property in error messages.
*/ */
export function expectToBeCorrectEntity<T extends object>(actual: { entity: T; name?: string }, expected: { entity: T; name?: string }): void { export function expectToBeCorrectEntity<T extends object>(
if (!actual.name) { actual: T,
actual.name = 'actual'; expected: T,
} propertyPrefix: string = ""
if (!expected.name) { ): void {
expected.name = 'expected'; for (const property in expected) {
} const prefixedProperty = propertyPrefix + property;
for (const property in expected.entity) {
if ( if (
property in IGNORE_PROPERTIES && property in IGNORE_PROPERTIES &&
expected.entity[property] !== undefined && // If we don't expect a certain value for a property, we assume it can be filled in by the database however it wants. expected[property] !== undefined && // If we don't expect a certain value for a property, we assume it can be filled in by the database however it wants.
typeof expected.entity[property] !== 'function' // Functions obviously are not persisted via the database typeof expected[property] !== 'function' // Functions obviously are not persisted via the database
) { ) {
if (!Object.prototype.hasOwnProperty.call(actual.entity, property)) { if (!Object.prototype.hasOwnProperty.call(actual, property)) {
throw new AssertionError({ throw new AssertionError({
message: `${expected.name} has defined property ${property}, but ${actual.name} is missing it.`, message: `Expected property ${prefixedProperty}, but it is missing.`,
}); });
} }
if (typeof expected.entity[property] === 'boolean') { if (typeof expected[property] === 'boolean') {
// Sometimes, booleans get represented by numbers 0 and 1 in the objects actual from the database. // Sometimes, booleans get represented by numbers 0 and 1 in the objects actual from the database.
if (Boolean(expected.entity[property]) !== Boolean(actual.entity[property])) { if (Boolean(expected[property]) !== Boolean(actual[property])) {
throw new AssertionError({ throw new AssertionError({
message: `${property} was ${expected.entity[property]} in ${expected.name}, message: `Expected ${prefixedProperty} to be ${expected[property]},
but ${actual.entity[property]} (${Boolean(expected.entity[property])}) in ${actual.name}`, but was ${actual[property]} (${Boolean(expected[property])}).`,
}); });
} }
} else if (typeof expected.entity[property] !== typeof actual.entity[property]) { } else if (typeof expected[property] !== typeof actual[property]) {
throw new AssertionError({ throw new AssertionError({
message: `${property} has type ${typeof expected.entity[property]} in ${expected.name}, but type ${typeof actual.entity[property]} in ${actual.name}.`, message: `${prefixedProperty} was expected to have type ${typeof expected[property]},`
+ `but had type ${typeof actual[property]}.`,
}); });
} else if (typeof expected.entity[property] === 'object') { } else if (typeof expected[property] === 'object') {
expectToBeCorrectEntity( expectToBeCorrectEntity(actual[property] as object, expected[property] as object, property);
{
name: actual.name + '.' + property,
entity: actual.entity[property] as object,
},
{
name: expected.name + '.' + property,
entity: expected.entity[property] as object,
}
);
} else { } else {
if (expected.entity[property] !== actual.entity[property]) { if (expected[property] !== actual[property]) {
throw new AssertionError({ throw new AssertionError({
message: `${property} was ${expected.entity[property]} in ${expected.name}, but ${actual.entity[property]} in ${actual.name}`, message: `${prefixedProperty} was expected to be ${expected[property]}, `
+ `but was ${actual[property]}.`,
}); });
} }
} }

View file

@ -6,5 +6,5 @@ import path from 'node:path';
* @param relPath Path of the asset relative to the test-assets folder. * @param relPath Path of the asset relative to the test-assets folder.
*/ */
export function loadTestAsset(relPath: string): Buffer { export function loadTestAsset(relPath: string): Buffer {
return fs.readFileSync(path.resolve(__dirname, `../test-assets/${relPath}`)); return fs.readFileSync(path.resolve(__dirname, `../test_assets/${relPath}`));
} }

View file

@ -1,9 +1,9 @@
import { LearningObjectExample } from '../learning-object-example'; import { LearningObjectExample } from '../learning-object-example';
import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; import { LearningObject } from '../../../../../src/entities/content/learning-object.entity';
import { Language } from '@dwengo-1/common/util/language'; import { Language } from '@dwengo-1/common/dist/util/language';
import { loadTestAsset } from '../../../test-utils/load-test-asset'; import { loadTestAsset } from '../../../../test-utils/load-test-asset';
import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type';
import { envVars, getEnvVar } from '../../../../src/util/envVars'; import { envVars, getEnvVar } from '../../../../../src/util/envVars';
/** /**
* Create a dummy learning object to be used in tests where multiple learning objects are needed (for example for use * Create a dummy learning object to be used in tests where multiple learning objects are needed (for example for use

View file

@ -1,12 +1,12 @@
import { LearningObjectExample } from '../learning-object-example'; import { LearningObjectExample } from '../learning-object-example';
import { Language } from '@dwengo-1/common/util/language'; import { Language } from '@dwengo-1/common/dist/util/language';
import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type';
import { loadTestAsset } from '../../../test-utils/load-test-asset'; import { loadTestAsset } from '../../../../test-utils/load-test-asset';
import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; import { LearningObject } from '../../../../../src/entities/content/learning-object.entity';
import { Attachment } from '../../../../src/entities/content/attachment.entity'; import { Attachment } from '../../../../../src/entities/content/attachment.entity';
import { envVars, getEnvVar } from '../../../../src/util/envVars'; import { envVars, getEnvVar } from '../../../../../src/util/envVars';
import { EducationalGoal } from '../../../../src/entities/content/educational-goal.entity'; import { EducationalGoal } from '../../../../../src/entities/content/educational-goal.entity';
import { ReturnValue } from '../../../../src/entities/content/return-value.entity'; import { ReturnValue } from '../../../../../src/entities/content/return-value.entity';
const ASSETS_PREFIX = 'learning-objects/pn-werkingnotebooks/'; const ASSETS_PREFIX = 'learning-objects/pn-werkingnotebooks/';

View file

@ -0,0 +1,2 @@
::MC basic::
Reflect on this learning path. What have you learned today? {}

View file

@ -1,7 +1,7 @@
<div class="learning-object-gift"> <div class="learning-object-gift">
<div id="gift-q1" class="gift-question"> <div id="gift-q1" class="gift-question">
<h2 id="gift-q1-title" class="gift-title">MC basic</h2> <h2 id="gift-q1-title" class="gift-title">MC basic</h2>
<p id="gift-q1-stem" class="gift-stem">How are you?</p> <p id="gift-q1-stem" class="gift-stem">Reflect on this learning path. What have you learned today?</p>
<textarea id="gift-q1-answer" class="gift-essay-answer"></textarea> <textarea id="gift-q1-answer" class="gift-essay-answer"></textarea>
</div> </div>
</div> </div>

View file

@ -1,9 +1,9 @@
import { LearningObjectExample } from '../learning-object-example'; import { LearningObjectExample } from '../learning-object-example';
import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; import { LearningObject } from '../../../../../src/entities/content/learning-object.entity';
import { loadTestAsset } from '../../../test-utils/load-test-asset'; import { loadTestAsset } from '../../../../test-utils/load-test-asset';
import { envVars, getEnvVar } from '../../../../src/util/envVars'; import { envVars, getEnvVar } from '../../../../../src/util/envVars';
import { Language } from '@dwengo-1/common/util/language'; import { Language } from '@dwengo-1/common/dist/util/language';
import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type';
const example: LearningObjectExample = { const example: LearningObjectExample = {
createLearningObject: () => { createLearningObject: () => {

View file

@ -1,5 +1,5 @@
::MC basic:: ::MC basic::
Are you following along well with the class? { Are you following along well? {
~No, it's very difficult to follow along. ~No, it's very difficult to follow along.
=Yes, no problem! =Yes, no problem!
} }

View file

@ -1,7 +1,7 @@
<div class="learning-object-gift"> <div class="learning-object-gift">
<div id="gift-q1" class="gift-question"> <div id="gift-q1" class="gift-question">
<h2 id="gift-q1-title" class="gift-title">MC basic</h2> <h2 id="gift-q1-title" class="gift-title">MC basic</h2>
<p id="gift-q1-stem" class="gift-stem">Are you following along well with the class?</p> <p id="gift-q1-stem" class="gift-stem">Are you following along well?</p>
<div class="gift-choice-div"> <div class="gift-choice-div">
<input value="0" name="gift-q1-choices" id="gift-q1-choice-0" type="radio"> <input value="0" name="gift-q1-choices" id="gift-q1-choice-0" type="radio">
<label for="gift-q1-choice-0">[object Object]</label> <label for="gift-q1-choice-0">[object Object]</label>

View file

@ -1,9 +1,9 @@
import { LearningObjectExample } from '../learning-object-example'; import { LearningObjectExample } from '../learning-object-example';
import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; import { LearningObject } from '../../../../../src/entities/content/learning-object.entity';
import { loadTestAsset } from '../../../test-utils/load-test-asset'; import { loadTestAsset } from '../../../../test-utils/load-test-asset';
import { envVars, getEnvVar } from '../../../../src/util/envVars'; import { envVars, getEnvVar } from '../../../../../src/util/envVars';
import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type';
import { Language } from '@dwengo-1/common/util/language'; import { Language } from '@dwengo-1/common/dist/util/language';
const example: LearningObjectExample = { const example: LearningObjectExample = {
createLearningObject: () => { createLearningObject: () => {

View file

@ -1,15 +1,41 @@
import { EntityManager } from '@mikro-orm/core'; import {EntityManager, RequiredEntityData} from '@mikro-orm/core';
import {LearningObject} from '../../../src/entities/content/learning-object.entity'; import {LearningObject} from '../../../src/entities/content/learning-object.entity';
import {Language} from '@dwengo-1/common/util/language'; import {Language} from '@dwengo-1/common/util/language';
import {DwengoContentType} from '../../../src/services/learning-objects/processing/content-type'; import {DwengoContentType} from '../../../src/services/learning-objects/processing/content-type';
import {ReturnValue} from '../../../src/entities/content/return-value.entity'; import {ReturnValue} from '../../../src/entities/content/return-value.entity';
import {envVars, getEnvVar} from "../../../src/util/envVars";
import {loadTestAsset} from "../../test-utils/load-test-asset";
export function makeTestLearningObjects(em: EntityManager): LearningObject[] { export function makeTestLearningObjects(em: EntityManager): LearningObject[] {
const returnValue: ReturnValue = new ReturnValue(); const returnValue: ReturnValue = new ReturnValue();
returnValue.callbackSchema = ''; returnValue.callbackSchema = '';
returnValue.callbackUrl = ''; returnValue.callbackUrl = '';
const learningObject01 = em.create(LearningObject, { const learningObject01 = em.create(LearningObject, testLearningObject01);
const learningObject02 = em.create(LearningObject, testLearningObject02);
const learningObject03 = em.create(LearningObject, testLearningObject03);
const learningObject04 = em.create(LearningObject, testLearningObject04);
const learningObject05 = em.create(LearningObject, testLearningObject05);
const learningObjectMultipleChoice = em.create(LearningObject, testLearningObjectMultipleChoice);
const learningObjectEssayQuestion= em.create(LearningObject, testLearningObjectEssayQuestion);
const learningObjectPnNotebooks = em.create(LearningObject, testLearningObjectPnNotebooks);
return [
learningObject01, learningObject02, learningObject03, learningObject04, learningObject05,
learningObjectMultipleChoice, learningObjectEssayQuestion, learningObjectPnNotebooks
];
}
export function createReturnValue(): ReturnValue {
const returnValue: ReturnValue = new ReturnValue();
returnValue.callbackSchema = '';
returnValue.callbackUrl = '';
return returnValue;
}
export const testLearningObject01: RequiredEntityData<LearningObject> = {
hruid: 'id01', hruid: 'id01',
language: Language.English, language: Language.English,
version: 1, version: 1,
@ -24,14 +50,14 @@ export function makeTestLearningObjects(em: EntityManager): LearningObject[] {
copyright: '', copyright: '',
license: '', license: '',
estimatedTime: 45, estimatedTime: 45,
returnValue: returnValue, returnValue: createReturnValue(),
available: true, available: true,
contentLocation: '', contentLocation: '',
attachments: [], attachments: [],
content: Buffer.from("there's a shadow just behind me, shrouding every step i take, making every promise empty pointing every finger at me"), content: Buffer.from("there's a shadow just behind me, shrouding every step i take, making every promise empty pointing every finger at me"),
}); };
const learningObject02 = em.create(LearningObject, { export const testLearningObject02: RequiredEntityData<LearningObject> = {
hruid: 'id02', hruid: 'id02',
language: Language.English, language: Language.English,
version: 1, version: 1,
@ -46,16 +72,16 @@ export function makeTestLearningObjects(em: EntityManager): LearningObject[] {
copyright: '', copyright: '',
license: '', license: '',
estimatedTime: 80, estimatedTime: 80,
returnValue: returnValue, returnValue: createReturnValue(),
available: true, available: true,
contentLocation: '', contentLocation: '',
attachments: [], attachments: [],
content: Buffer.from( content: Buffer.from(
"I've been crawling on my belly clearing out what could've been I've been wallowing in my own confused and insecure delusions" "I've been crawling on my belly clearing out what could've been I've been wallowing in my own confused and insecure delusions"
), ),
}); };
const learningObject03 = em.create(LearningObject, { export const testLearningObject03: RequiredEntityData<LearningObject> = {
hruid: 'id03', hruid: 'id03',
language: Language.English, language: Language.English,
version: 1, version: 1,
@ -70,7 +96,7 @@ export function makeTestLearningObjects(em: EntityManager): LearningObject[] {
copyright: '', copyright: '',
license: '', license: '',
estimatedTime: 55, estimatedTime: 55,
returnValue: returnValue, returnValue: createReturnValue(),
available: true, available: true,
contentLocation: '', contentLocation: '',
attachments: [], attachments: [],
@ -80,9 +106,9 @@ export function makeTestLearningObjects(em: EntityManager): LearningObject[] {
come back and see me later next patient please \ come back and see me later next patient please \
send in another victim of industrial disease' send in another victim of industrial disease'
), ),
}); };
const learningObject04 = em.create(LearningObject, { export const testLearningObject04: RequiredEntityData<LearningObject> = {
hruid: 'id04', hruid: 'id04',
language: Language.English, language: Language.English,
version: 1, version: 1,
@ -97,7 +123,7 @@ export function makeTestLearningObjects(em: EntityManager): LearningObject[] {
copyright: '', copyright: '',
license: '', license: '',
estimatedTime: 55, estimatedTime: 55,
returnValue: returnValue, returnValue: createReturnValue(),
available: true, available: true,
contentLocation: '', contentLocation: '',
attachments: [], attachments: [],
@ -107,9 +133,9 @@ export function makeTestLearningObjects(em: EntityManager): LearningObject[] {
I had the one-arm bandit fever \ I had the one-arm bandit fever \
There was an arrow through my heart and my soul' There was an arrow through my heart and my soul'
), ),
}); };
const learningObject05 = em.create(LearningObject, { export const testLearningObject05: RequiredEntityData<LearningObject> = {
hruid: 'id05', hruid: 'id05',
language: Language.English, language: Language.English,
version: 1, version: 1,
@ -124,12 +150,103 @@ export function makeTestLearningObjects(em: EntityManager): LearningObject[] {
copyright: '', copyright: '',
license: '', license: '',
estimatedTime: 55, estimatedTime: 55,
returnValue: returnValue, returnValue: createReturnValue(),
available: true, available: true,
contentLocation: '', contentLocation: '',
attachments: [], attachments: [],
content: Buffer.from('calling Elvis, is anybody home, calling elvis, I am here all alone'), content: Buffer.from('calling Elvis, is anybody home, calling elvis, I am here all alone'),
}); };
return [learningObject01, learningObject02, learningObject03, learningObject04, learningObject05]; export const testLearningObjectMultipleChoice: RequiredEntityData<LearningObject> = {
hruid: `${getEnvVar(envVars.UserContentPrefix)}test_multiple_choice`,
language: Language.English,
version: 1,
title: "Self-evaluation",
description: "Time to evaluate how well you understand what you've learned so far.",
keywords: ["test"],
teacherExclusive: false,
skosConcepts: [],
educationalGoals: [],
copyright: "Groep 1 SEL-2 2025",
license: "CC0",
difficulty: 1,
estimatedTime: 1,
attachments: [],
available: true,
targetAges: [10, 11, 12, 13, 14, 15, 16, 17, 18],
admins: [],
contentType: DwengoContentType.GIFT,
content: loadTestAsset('content/learning-object-resources/test-multiple-choice/content.txt'),
returnValue: {
callbackUrl: `%SUBMISSION%`,
callbackSchema: '["antwoord vraag 1"]',
}
};
export const testLearningObjectEssayQuestion: RequiredEntityData<LearningObject> = {
hruid: `${getEnvVar(envVars.UserContentPrefix)}test_essay_question`,
language: Language.English,
version: 1,
title: "Reflection",
description: "Reflect on your learning progress.",
keywords: ["test"],
teacherExclusive: false,
skosConcepts: [],
educationalGoals: [],
copyright: "Groep 1 SEL-2 2025",
license: "CC0",
difficulty: 1,
estimatedTime: 1,
attachments: [],
available: true,
targetAges: [10, 11, 12, 13, 14, 15, 16, 17, 18],
admins: [],
contentType: DwengoContentType.GIFT,
content: loadTestAsset('content/learning-object-resources/test-essay/content.txt'),
returnValue: {
callbackUrl: `%SUBMISSION%`,
callbackSchema: '["antwoord vraag 1"]',
}
};
export const testLearningObjectPnNotebooks: RequiredEntityData<LearningObject> = {
hruid: `${getEnvVar(envVars.UserContentPrefix)}pn_werkingnotebooks`,
version: 3,
language: Language.Dutch,
title: "Werken met notebooks",
description: "Leren werken met notebooks",
keywords: ["Python", "KIKS", "Wiskunde", "STEM", "AI"],
targetAges: [14, 15, 16, 17, 18],
admins: [],
copyright: "dwengo",
educationalGoals: [],
license: "dwengo",
contentType: DwengoContentType.TEXT_MARKDOWN,
difficulty: 3,
estimatedTime: 10,
uuid: "2adf9929-b424-4650-bf60-186f730d38ab",
teacherExclusive: false,
skosConcepts: [
"http://ilearn.ilabt.imec.be/vocab/curr1/s-vaktaal",
"http://ilearn.ilabt.imec.be/vocab/curr1/s-digitale-media-en-toepassingen",
"http://ilearn.ilabt.imec.be/vocab/curr1/s-computers-en-systemen",
],
attachments: [
{
name: "dwengo.png",
mimeType: "image/png",
content: loadTestAsset("/content/learning-object-resources/pn-werkingnotebooks/dwengo.png")
},
{
name: "Knop.png",
mimeType: "image/png",
content: loadTestAsset("/content/learning-object-resources/pn-werkingnotebooks/Knop.png")
}
],
available: false,
content: loadTestAsset("/content/learning-object-resources/pn-werkingnotebooks/content.md"),
returnValue: {
callbackUrl: "%SUBMISSION%",
callbackSchema: "[]"
}
} }

View file

@ -1,100 +1,237 @@
import {EntityManager} from '@mikro-orm/core'; import {EntityManager} from '@mikro-orm/core';
import {LearningPath} from '../../../src/entities/content/learning-path.entity'; import {LearningPath} from '../../../src/entities/content/learning-path.entity';
import {Language} from '@dwengo-1/common/util/language'; import {Language} from '@dwengo-1/common/util/language';
import { LearningPathTransition } from '../../../src/entities/content/learning-path-transition.entity'; import {mapToLearningPath} from "../../../src/services/learning-paths/learning-path-service";
import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; import {envVars, getEnvVar} from "../../../src/util/envVars";
import {LearningPath as LearningPathDTO} from "@dwengo-1/common/interfaces/learning-content";
import {
testLearningObject01, testLearningObject02, testLearningObject03, testLearningObject04, testLearningObject05,
testLearningObjectEssayQuestion,
testLearningObjectMultipleChoice, testLearningObjectPnNotebooks
} from "./learning-objects.testdata";
export function makeTestLearningPaths(em: EntityManager): LearningPath[] { export function makeTestLearningPaths(_em: EntityManager): LearningPath[] {
const learningPathNode01: LearningPathNode = new LearningPathNode(); const learningPath01 = mapToLearningPath(testLearningPath01, []);
const learningPathNode02: LearningPathNode = new LearningPathNode(); const learningPath02 = mapToLearningPath(testLearningPath02, []);
const learningPathNode03: LearningPathNode = new LearningPathNode();
const learningPathNode04: LearningPathNode = new LearningPathNode();
const learningPathNode05: LearningPathNode = new LearningPathNode();
const transitions01: LearningPathTransition = new LearningPathTransition(); const partiallyDatabasePartiallyDwengoApiLearningPath
const transitions02: LearningPathTransition = new LearningPathTransition(); = mapToLearningPath(testPartiallyDatabaseAndPartiallyDwengoApiLearningPath, [])
const transitions03: LearningPathTransition = new LearningPathTransition(); const learningPathWithConditions = mapToLearningPath(testLearningPathWithConditions, [])
const transitions04: LearningPathTransition = new LearningPathTransition();
const transitions05: LearningPathTransition = new LearningPathTransition();
transitions01.condition = 'true'; return [
transitions01.next = learningPathNode02; learningPath01,
learningPath02,
transitions02.condition = 'true'; partiallyDatabasePartiallyDwengoApiLearningPath,
transitions02.next = learningPathNode02; learningPathWithConditions
transitions03.condition = 'true';
transitions03.next = learningPathNode04;
transitions04.condition = 'true';
transitions04.next = learningPathNode05;
transitions05.condition = 'true';
transitions05.next = learningPathNode05;
learningPathNode01.instruction = '';
learningPathNode01.language = Language.English;
learningPathNode01.learningObjectHruid = 'id01';
learningPathNode01.startNode = true;
learningPathNode01.transitions = [transitions01];
learningPathNode01.version = 1;
learningPathNode02.instruction = '';
learningPathNode02.language = Language.English;
learningPathNode02.learningObjectHruid = 'id02';
learningPathNode02.startNode = false;
learningPathNode02.transitions = [transitions02];
learningPathNode02.version = 1;
learningPathNode03.instruction = '';
learningPathNode03.language = Language.English;
learningPathNode03.learningObjectHruid = 'id03';
learningPathNode03.startNode = true;
learningPathNode03.transitions = [transitions03];
learningPathNode03.version = 1;
learningPathNode04.instruction = '';
learningPathNode04.language = Language.English;
learningPathNode04.learningObjectHruid = 'id04';
learningPathNode04.startNode = false;
learningPathNode04.transitions = [transitions04];
learningPathNode04.version = 1;
learningPathNode05.instruction = '';
learningPathNode05.language = Language.English;
learningPathNode05.learningObjectHruid = 'id05';
learningPathNode05.startNode = false;
learningPathNode05.transitions = [transitions05];
learningPathNode05.version = 1;
const nodes01: LearningPathNode[] = [
// LearningPathNode01,
// LearningPathNode02,
]; ];
const learningPath01 = em.create(LearningPath, { }
hruid: 'id01',
language: Language.English, const nowString = new Date().toString();
admins: [],
title: 'repertoire Tool', export const testLearningPath01: LearningPathDTO = {
description: 'all about Tool', keywords: "test",
image: null, target_ages: [16, 17, 18],
nodes: nodes01, hruid: "id01",
}); language: Language.English,
title: "repertoire Tool",
const nodes02: LearningPathNode[] = [ description: "all about Tool",
// LearningPathNode03, nodes: [
// LearningPathNode04, {
// LearningPathNode05, learningobject_hruid: testLearningObject01.hruid,
]; language: testLearningObject01.language,
const learningPath02 = em.create(LearningPath, { version: testLearningObject01.version,
hruid: 'id02', start_node: true,
language: Language.English, created_at: nowString,
admins: [], updatedAt: nowString,
title: 'repertoire Dire Straits', transitions: [
description: 'all about Dire Straits', {
image: null, next: {
nodes: nodes02, hruid: testLearningObject02.hruid,
}); language: testLearningObject02.language,
version: testLearningObject02.version
return [learningPath01, learningPath02]; }
}
]
},
{
learningobject_hruid: testLearningObject02.hruid,
language: testLearningObject02.language,
version: testLearningObject02.version,
created_at: nowString,
updatedAt: nowString,
transitions: []
}
]
};
export const testLearningPath02: LearningPathDTO = {
keywords: "test",
target_ages: [16, 17, 18],
hruid: "id02",
language: Language.English,
title: "repertoire Dire Straits",
description: "all about Dire Straits",
nodes: [
{
learningobject_hruid: testLearningObject03.hruid,
language: testLearningObject03.language,
version: testLearningObject03.version,
start_node: true,
created_at: nowString,
updatedAt: nowString,
transitions: [
{
next: {
hruid: testLearningObject04.hruid,
language: testLearningObject04.language,
version: testLearningObject04.version
}
}
]
},
{
learningobject_hruid: testLearningObject04.hruid,
language: testLearningObject04.language,
version: testLearningObject04.version,
created_at: nowString,
updatedAt: nowString,
transitions: [
{
next: {
hruid: testLearningObject05.hruid,
language: testLearningObject05.language,
version: testLearningObject05.version
}
}
]
},
{
learningobject_hruid: testLearningObject05.hruid,
language: testLearningObject05.language,
version: testLearningObject05.version,
created_at: nowString,
updatedAt: nowString,
transitions: []
}
]
};
export const testPartiallyDatabaseAndPartiallyDwengoApiLearningPath: LearningPathDTO = {
hruid: `${getEnvVar(envVars.UserContentPrefix)}pn_werking`,
title: "Werken met notebooks",
language: Language.Dutch,
description: 'Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?',
keywords: "Python KIKS Wiskunde STEM AI",
target_ages: [14, 15, 16, 17, 18],
nodes: [
{
learningobject_hruid: testLearningObjectPnNotebooks.hruid,
language: testLearningObjectPnNotebooks.language,
version: testLearningObjectPnNotebooks.version,
start_node: true,
created_at: nowString,
updatedAt: nowString,
transitions: [
{
default: true,
next: {
hruid: "pn_werkingnotebooks2",
language: Language.Dutch,
version: 3
}
}
]
},
{
learningobject_hruid: "pn_werkingnotebooks2",
language: Language.Dutch,
version: 3,
created_at: nowString,
updatedAt: nowString,
transitions: [
{
default: true,
next: {
hruid: "pn_werkingnotebooks3",
language: Language.Dutch,
version: 3
}
}
]
},
{
learningobject_hruid: "pn_werkingnotebooks3",
language: Language.Dutch,
version: 3,
created_at: nowString,
updatedAt: nowString,
transitions: []
}
]
}
export const testLearningPathWithConditions: LearningPathDTO = {
hruid: `${getEnvVar(envVars.UserContentPrefix)}test_conditions`,
language: Language.English,
title: 'Example learning path with conditional transitions',
description: 'This learning path was made for the purpose of testing conditional transitions',
keywords: "test",
target_ages: [18, 19, 20, 21],
nodes: [
{
learningobject_hruid: testLearningObjectMultipleChoice.hruid,
language: testLearningObjectMultipleChoice.language,
version: testLearningObjectMultipleChoice.version,
start_node: true,
created_at: nowString,
updatedAt: nowString,
transitions: [
{
// If the answer to the first question was the first one (It's difficult to follow along):
condition: '$[?(@[0] == 0)]',
next: {
//... we let the student do an extra exercise.
hruid: testLearningObject01.hruid,
language: testLearningObject01.language,
version: testLearningObject01.version
}
},
{
// If the answer to the first question was the second one (I can follow along):
condition: '$[?(@[0] == 1)]',
next: {
//... we let the student right through to the final question.
hruid: testLearningObjectEssayQuestion.hruid,
language: testLearningObjectEssayQuestion.language,
version: testLearningObjectEssayQuestion.version
}
}
]
},
{
learningobject_hruid: testLearningObject01.hruid,
language: testLearningObject01.language,
version: testLearningObject01.version,
created_at: nowString,
updatedAt: nowString,
transitions: [
{
default: true,
next: {
hruid: testLearningObjectEssayQuestion.hruid,
language: testLearningObjectEssayQuestion.language,
version: testLearningObjectEssayQuestion.version
}
}
]
},
{
learningobject_hruid: testLearningObjectEssayQuestion.hruid,
language: testLearningObjectEssayQuestion.language,
version: testLearningObjectEssayQuestion.version,
created_at: nowString,
updatedAt: nowString,
transitions: []
}
]
} }

View file

@ -9,6 +9,7 @@ export interface Transition {
version: number; version: number;
language: string; language: string;
}; };
condition?: string;
} }
export interface LearningObjectIdentifierDTO { export interface LearningObjectIdentifierDTO {
@ -18,7 +19,7 @@ export interface LearningObjectIdentifierDTO {
} }
export interface LearningObjectNode { export interface LearningObjectNode {
_id: string; _id?: string;
learningobject_hruid: string; learningobject_hruid: string;
version: number; version: number;
language: Language; language: Language;
@ -30,20 +31,20 @@ export interface LearningObjectNode {
} }
export interface LearningPath { export interface LearningPath {
_id: string; _id?: string;
language: string; language: string;
hruid: string; hruid: string;
title: string; title: string;
description: string; description: string;
image?: string; // Image might be missing, so it's optional image?: string; // Image might be missing, so it's optional
num_nodes: number; num_nodes?: number;
num_nodes_left: number; num_nodes_left?: number;
nodes: LearningObjectNode[]; nodes: LearningObjectNode[];
keywords: string; keywords: string;
target_ages: number[]; target_ages: number[];
min_age: number; min_age?: number;
max_age: number; max_age?: number;
__order: number; __order?: number;
} }
export interface LearningPathIdentifier { export interface LearningPathIdentifier {
@ -62,8 +63,8 @@ export interface ReturnValue {
} }
export interface LearningObjectMetadata { export interface LearningObjectMetadata {
_id: string; _id?: string;
uuid: string; uuid?: string;
hruid: string; hruid: string;
version: number; version: number;
language: Language; language: Language;
@ -84,7 +85,7 @@ export interface LearningObjectMetadata {
export interface FilteredLearningObject { export interface FilteredLearningObject {
key: string; key: string;
_id: string; _id?: string;
uuid: string; uuid: string;
version: number; version: number;
title: string; title: string;