merge: fixed merge conflicts with dev
This commit is contained in:
		
						commit
						faa2f58145
					
				
					 165 changed files with 3948 additions and 3282 deletions
				
			
		|  | @ -16,7 +16,7 @@ describe('AssignmentRepository', () => { | |||
| 
 | ||||
|     it('should return the requested assignment', async () => { | ||||
|         const class_ = await classRepository.findById('34d484a1-295f-4e9f-bfdc-3e7a23d86a89'); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 2); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 21001); | ||||
| 
 | ||||
|         expect(assignment).toBeTruthy(); | ||||
|         expect(assignment!.title).toBe('tool'); | ||||
|  | @ -35,7 +35,7 @@ describe('AssignmentRepository', () => { | |||
|         const result = await assignmentRepository.findAllByResponsibleTeacher('testleerkracht1'); | ||||
|         const resultIds = result.map((it) => it.id).sort((a, b) => (a ?? 0) - (b ?? 0)); | ||||
| 
 | ||||
|         expect(resultIds).toEqual([1, 3, 4]); | ||||
|         expect(resultIds).toEqual([21000, 21002, 21003, 21004]); | ||||
|     }); | ||||
| 
 | ||||
|     it('should not find removed assignment', async () => { | ||||
|  |  | |||
|  | @ -19,16 +19,16 @@ describe('GroupRepository', () => { | |||
| 
 | ||||
|     it('should return the requested group', async () => { | ||||
|         const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 1); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 21000); | ||||
| 
 | ||||
|         const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 1); | ||||
|         const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 21001); | ||||
| 
 | ||||
|         expect(group).toBeTruthy(); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return all groups for assignment', async () => { | ||||
|         const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 1); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 21000); | ||||
| 
 | ||||
|         const groups = await groupRepository.findAllGroupsForAssignment(assignment!); | ||||
| 
 | ||||
|  | @ -38,9 +38,9 @@ describe('GroupRepository', () => { | |||
| 
 | ||||
|     it('should not find removed group', async () => { | ||||
|         const class_ = await classRepository.findById('34d484a1-295f-4e9f-bfdc-3e7a23d86a89'); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 2); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 21001); | ||||
| 
 | ||||
|         await groupRepository.deleteByAssignmentAndGroupNumber(assignment!, 1); | ||||
|         await groupRepository.deleteByAssignmentAndGroupNumber(assignment!, 21001); | ||||
| 
 | ||||
|         const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 1); | ||||
| 
 | ||||
|  |  | |||
|  | @ -54,8 +54,8 @@ describe('SubmissionRepository', () => { | |||
|     it('should find the most recent submission for a group', async () => { | ||||
|         const id = new LearningObjectIdentifier('id03', Language.English, 1); | ||||
|         const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 1); | ||||
|         const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 1); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 21000); | ||||
|         const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 21001); | ||||
|         const submission = await submissionRepository.findMostRecentSubmissionForGroup(id, group!); | ||||
| 
 | ||||
|         expect(submission).toBeTruthy(); | ||||
|  | @ -67,7 +67,7 @@ describe('SubmissionRepository', () => { | |||
|     let loId: LearningObjectIdentifier; | ||||
|     it('should find all submissions for a certain learning object and assignment', async () => { | ||||
|         clazz = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         assignment = await assignmentRepository.findByClassAndId(clazz!, 1); | ||||
|         assignment = await assignmentRepository.findByClassAndId(clazz!, 21000); | ||||
|         loId = { | ||||
|             hruid: 'id02', | ||||
|             language: Language.English, | ||||
|  | @ -91,9 +91,9 @@ describe('SubmissionRepository', () => { | |||
|         expect(result[2].submissionNumber).toBe(3); | ||||
|     }); | ||||
| 
 | ||||
|     it("should find only the submissions for a certain learning object and assignment made for the user's group", async () => { | ||||
|         const result = await submissionRepository.findAllSubmissionsForLearningObjectAndAssignment(loId, assignment!, 'Tool'); | ||||
|         // (student Tool is in group #2)
 | ||||
|     it('should find only the submissions for a certain learning object and assignment made for the given group', async () => { | ||||
|         const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 21002); | ||||
|         const result = await submissionRepository.findAllSubmissionsForLearningObjectAndGroup(loId, group!); | ||||
| 
 | ||||
|         expect(result).toHaveLength(1); | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,66 +2,57 @@ import { beforeAll, describe, expect, it } from 'vitest'; | |||
| import { setupTestApp } from '../../setup-tests.js'; | ||||
| import { getAttachmentRepository, getLearningObjectRepository } from '../../../src/data/repositories.js'; | ||||
| import { AttachmentRepository } from '../../../src/data/content/attachment-repository.js'; | ||||
| import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.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 { Attachment } from '../../../src/entities/content/attachment.entity.js'; | ||||
| import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier.js'; | ||||
| 
 | ||||
| const NEWER_TEST_SUFFIX = 'nEweR'; | ||||
| 
 | ||||
| async function createTestLearningObjects(learningObjectRepo: LearningObjectRepository): Promise<{ | ||||
|     older: LearningObject; | ||||
|     newer: LearningObject; | ||||
| }> { | ||||
|     const olderExample = example.createLearningObject(); | ||||
|     await learningObjectRepo.save(olderExample); | ||||
| 
 | ||||
|     const newerExample = example.createLearningObject(); | ||||
|     newerExample.title = 'Newer example'; | ||||
|     newerExample.version = 100; | ||||
| 
 | ||||
|     return { | ||||
|         older: olderExample, | ||||
|         newer: newerExample, | ||||
|     }; | ||||
| } | ||||
| import { testLearningObjectPnNotebooks } from '../../test_assets/content/learning-objects.testdata'; | ||||
| import { v4 as uuidV4 } from 'uuid'; | ||||
| 
 | ||||
| describe('AttachmentRepository', () => { | ||||
|     let attachmentRepo: AttachmentRepository; | ||||
|     let exampleLearningObjects: { older: LearningObject; newer: LearningObject }; | ||||
|     let newLearningObject: LearningObject; | ||||
|     let attachmentsOlderLearningObject: Attachment[]; | ||||
| 
 | ||||
|     beforeAll(async () => { | ||||
|         await setupTestApp(); | ||||
| 
 | ||||
|         attachmentsOlderLearningObject = testLearningObjectPnNotebooks.attachments as Attachment[]; | ||||
| 
 | ||||
|         attachmentRepo = getAttachmentRepository(); | ||||
|         exampleLearningObjects = await createTestLearningObjects(getLearningObjectRepository()); | ||||
|     }); | ||||
|         const learningObjectRepo = getLearningObjectRepository(); | ||||
| 
 | ||||
|     it('can add attachments to learning objects without throwing an error', async () => { | ||||
|         attachmentsOlderLearningObject = Object.values(example.createAttachment).map((fn) => fn(exampleLearningObjects.older)); | ||||
|         const newLearningObjectData = structuredClone(testLearningObjectPnNotebooks); | ||||
|         newLearningObjectData.title = 'Newer example'; | ||||
|         newLearningObjectData.version = 101; | ||||
|         newLearningObjectData.attachments = []; | ||||
|         newLearningObjectData.uuid = uuidV4(); | ||||
|         newLearningObjectData.content = Buffer.from('Content of the newer example'); | ||||
| 
 | ||||
|         await Promise.all(attachmentsOlderLearningObject.map(async (attachment) => attachmentRepo.save(attachment))); | ||||
|         newLearningObject = learningObjectRepo.create(newLearningObjectData); | ||||
|         await learningObjectRepo.save(newLearningObject); | ||||
|     }); | ||||
| 
 | ||||
|     let attachmentOnlyNewer: Attachment; | ||||
|     it('allows us to add attachments with the same name to a different learning object without throwing an error', async () => { | ||||
|         attachmentOnlyNewer = Object.values(example.createAttachment)[0](exampleLearningObjects.newer); | ||||
|         attachmentOnlyNewer.content.write(NEWER_TEST_SUFFIX); | ||||
|         attachmentOnlyNewer = structuredClone(attachmentsOlderLearningObject[0]); | ||||
|         attachmentOnlyNewer.learningObject = newLearningObject; | ||||
|         attachmentOnlyNewer.content = Buffer.from('New attachment content'); | ||||
| 
 | ||||
|         await attachmentRepo.save(attachmentOnlyNewer); | ||||
|         await attachmentRepo.save(attachmentRepo.create(attachmentOnlyNewer)); | ||||
|     }); | ||||
| 
 | ||||
|     let olderLearningObjectId: LearningObjectIdentifier; | ||||
|     it('returns the correct attachment when queried by learningObjectId and attachment name', async () => { | ||||
|         olderLearningObjectId = { | ||||
|             hruid: exampleLearningObjects.older.hruid, | ||||
|             language: exampleLearningObjects.older.language, | ||||
|             version: exampleLearningObjects.older.version, | ||||
|             hruid: testLearningObjectPnNotebooks.hruid, | ||||
|             language: testLearningObjectPnNotebooks.language, | ||||
|             version: testLearningObjectPnNotebooks.version, | ||||
|         }; | ||||
| 
 | ||||
|         const result = await attachmentRepo.findByLearningObjectIdAndName(olderLearningObjectId, attachmentsOlderLearningObject[0].name); | ||||
|         expect(result).toBe(attachmentsOlderLearningObject[0]); | ||||
|         expect(result).not.toBeNull(); | ||||
|         expect(result!.name).toEqual(attachmentsOlderLearningObject[0].name); | ||||
|         expect(result!.content).toEqual(attachmentsOlderLearningObject[0].content); | ||||
|     }); | ||||
| 
 | ||||
|     it('returns null when queried by learningObjectId and non-existing attachment name', async () => { | ||||
|  | @ -71,10 +62,12 @@ describe('AttachmentRepository', () => { | |||
| 
 | ||||
|     it('returns the newer version of the attachment when only queried by hruid, language and attachment name (but not version)', async () => { | ||||
|         const result = await attachmentRepo.findByMostRecentVersionOfLearningObjectAndName( | ||||
|             exampleLearningObjects.older.hruid, | ||||
|             exampleLearningObjects.older.language, | ||||
|             testLearningObjectPnNotebooks.hruid, | ||||
|             testLearningObjectPnNotebooks.language, | ||||
|             attachmentOnlyNewer.name | ||||
|         ); | ||||
|         expect(result).toBe(attachmentOnlyNewer); | ||||
|         expect(result).not.toBeNull(); | ||||
|         expect(result!.name).toEqual(attachmentOnlyNewer.name); | ||||
|         expect(result!.content).toEqual(attachmentOnlyNewer.content); | ||||
|     }); | ||||
| }); | ||||
|  |  | |||
|  | @ -1,28 +1,21 @@ | |||
| import { beforeAll, describe, expect, it } from 'vitest'; | ||||
| import { setupTestApp } from '../../setup-tests.js'; | ||||
| import { getAttachmentRepository, getLearningObjectRepository } from '../../../src/data/repositories.js'; | ||||
| import { getAttachmentRepository } from '../../../src/data/repositories.js'; | ||||
| import { AttachmentRepository } from '../../../src/data/content/attachment-repository.js'; | ||||
| import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.js'; | ||||
| import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier.js'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import { testLearningObject02 } from '../../test_assets/content/learning-objects.testdata'; | ||||
| 
 | ||||
| describe('AttachmentRepository', () => { | ||||
|     let attachmentRepository: AttachmentRepository; | ||||
|     let learningObjectRepository: LearningObjectRepository; | ||||
| 
 | ||||
|     beforeAll(async () => { | ||||
|         await setupTestApp(); | ||||
|         attachmentRepository = getAttachmentRepository(); | ||||
|         learningObjectRepository = getLearningObjectRepository(); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return the requested attachment', async () => { | ||||
|         const id = new LearningObjectIdentifier('id02', Language.English, 1); | ||||
|         const learningObject = await learningObjectRepository.findByIdentifier(id); | ||||
| 
 | ||||
|         const attachment = await attachmentRepository.findByMostRecentVersionOfLearningObjectAndName( | ||||
|             learningObject!.hruid, | ||||
|             Language.English, | ||||
|             testLearningObject02.hruid, | ||||
|             testLearningObject02.language, | ||||
|             'attachment01' | ||||
|         ); | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,48 +2,33 @@ import { beforeAll, describe, it, expect } from 'vitest'; | |||
| import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.js'; | ||||
| import { setupTestApp } from '../../setup-tests.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 { expectToBeCorrectEntity } from '../../test-utils/expectations.js'; | ||||
| import { testLearningObject01, testLearningObject02, testLearningObject03 } from '../../test_assets/content/learning-objects.testdata'; | ||||
| import { v4 } from 'uuid'; | ||||
| 
 | ||||
| describe('LearningObjectRepository', () => { | ||||
|     let learningObjectRepository: LearningObjectRepository; | ||||
| 
 | ||||
|     let exampleLearningObject: LearningObject; | ||||
| 
 | ||||
|     beforeAll(async () => { | ||||
|         await setupTestApp(); | ||||
|         learningObjectRepository = getLearningObjectRepository(); | ||||
|     }); | ||||
| 
 | ||||
|     it('should be able to add a learning object to it without an error', async () => { | ||||
|         exampleLearningObject = example.createLearningObject(); | ||||
|         await learningObjectRepository.insert(exampleLearningObject); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return the learning object when queried by id', async () => { | ||||
|     it('should return a learning object when queried by id', async () => { | ||||
|         const result = await learningObjectRepository.findByIdentifier({ | ||||
|             hruid: exampleLearningObject.hruid, | ||||
|             language: exampleLearningObject.language, | ||||
|             version: exampleLearningObject.version, | ||||
|             hruid: testLearningObject01.hruid, | ||||
|             language: testLearningObject02.language, | ||||
|             version: testLearningObject03.version, | ||||
|         }); | ||||
|         expect(result).toBeInstanceOf(LearningObject); | ||||
|         expectToBeCorrectEntity( | ||||
|             { | ||||
|                 name: 'actual', | ||||
|                 entity: result!, | ||||
|             }, | ||||
|             { | ||||
|                 name: 'expected', | ||||
|                 entity: exampleLearningObject, | ||||
|             } | ||||
|         ); | ||||
|         expectToBeCorrectEntity(result!, testLearningObject01); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return null when non-existing version is queried', async () => { | ||||
|         const result = await learningObjectRepository.findByIdentifier({ | ||||
|             hruid: exampleLearningObject.hruid, | ||||
|             language: exampleLearningObject.language, | ||||
|             hruid: testLearningObject01.hruid, | ||||
|             language: testLearningObject01.language, | ||||
|             version: 100, | ||||
|         }); | ||||
|         expect(result).toBe(null); | ||||
|  | @ -52,9 +37,12 @@ describe('LearningObjectRepository', () => { | |||
|     let newerExample: LearningObject; | ||||
| 
 | ||||
|     it('should allow a learning object with the same id except a different version to be added', async () => { | ||||
|         newerExample = example.createLearningObject(); | ||||
|         newerExample.version = 10; | ||||
|         newerExample.title += ' (nieuw)'; | ||||
|         const testLearningObject01Newer = structuredClone(testLearningObject01); | ||||
|         testLearningObject01Newer.version = 10; | ||||
|         testLearningObject01Newer.title += ' (nieuw)'; | ||||
|         testLearningObject01Newer.uuid = v4(); | ||||
|         testLearningObject01Newer.content = Buffer.from('This is the new content.'); | ||||
|         newerExample = learningObjectRepository.create(testLearningObject01Newer); | ||||
|         await learningObjectRepository.save(newerExample); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -66,7 +54,7 @@ describe('LearningObjectRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     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); | ||||
|     }); | ||||
| }); | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { getLearningObjectRepository } from '../../../src/data/repositories'; | |||
| import { setupTestApp } from '../../setup-tests'; | ||||
| import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import { testLearningObject01 } from '../../test_assets/content/learning-objects.testdata'; | ||||
| 
 | ||||
| describe('LearningObjectRepository', () => { | ||||
|     let learningObjectRepository: LearningObjectRepository; | ||||
|  | @ -13,8 +14,8 @@ describe('LearningObjectRepository', () => { | |||
|         learningObjectRepository = getLearningObjectRepository(); | ||||
|     }); | ||||
| 
 | ||||
|     const id01 = new LearningObjectIdentifier('id01', Language.English, 1); | ||||
|     const id02 = new LearningObjectIdentifier('test_id', Language.English, 1); | ||||
|     const id01 = new LearningObjectIdentifier(testLearningObject01.hruid, testLearningObject01.language, testLearningObject01.version); | ||||
|     const id02 = new LearningObjectIdentifier('non_existing_id', Language.English, 1); | ||||
| 
 | ||||
|     it('should return the learning object that matches identifier 1', async () => { | ||||
|         const learningObject = await learningObjectRepository.findByIdentifier(id01); | ||||
|  |  | |||
|  | @ -2,41 +2,27 @@ import { beforeAll, describe, expect, it } from 'vitest'; | |||
| import { setupTestApp } from '../../setup-tests.js'; | ||||
| import { getLearningPathRepository } from '../../../src/data/repositories.js'; | ||||
| import { LearningPathRepository } from '../../../src/data/content/learning-path-repository.js'; | ||||
| import example from '../../test-assets/learning-paths/pn-werking-example.js'; | ||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity.js'; | ||||
| import { expectToBeCorrectEntity } from '../../test-utils/expectations.js'; | ||||
| import { expectToBeCorrectEntity, expectToHaveFoundNothing, expectToHaveFoundPrecisely } from '../../test-utils/expectations.js'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| 
 | ||||
| function expectToHaveFoundPrecisely(expected: LearningPath, result: LearningPath[]): void { | ||||
|     expect(result).toHaveProperty('length'); | ||||
|     expect(result.length).toBe(1); | ||||
|     expectToBeCorrectEntity({ entity: result[0] }, { entity: expected }); | ||||
| } | ||||
| 
 | ||||
| function expectToHaveFoundNothing(result: LearningPath[]): void { | ||||
|     expect(result).toHaveProperty('length'); | ||||
|     expect(result.length).toBe(0); | ||||
| } | ||||
| import { testLearningPath01 } from '../../test_assets/content/learning-paths.testdata'; | ||||
| import { mapToLearningPath } from '../../../src/services/learning-paths/learning-path-service'; | ||||
| 
 | ||||
| describe('LearningPathRepository', () => { | ||||
|     let learningPathRepo: LearningPathRepository; | ||||
|     let examplePath: LearningPath; | ||||
| 
 | ||||
|     beforeAll(async () => { | ||||
|         await setupTestApp(); | ||||
|         learningPathRepo = getLearningPathRepository(); | ||||
| 
 | ||||
|         examplePath = mapToLearningPath(testLearningPath01, []); | ||||
|     }); | ||||
| 
 | ||||
|     let examplePath: LearningPath; | ||||
| 
 | ||||
|     it('should be able to add a learning path without throwing an error', async () => { | ||||
|         examplePath = example.createLearningPath(); | ||||
|         await learningPathRepo.insert(examplePath); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return the added path when it is queried by hruid and language', async () => { | ||||
|         const result = await learningPathRepo.findByHruidAndLanguage(examplePath.hruid, examplePath.language); | ||||
|     it('should return a learning path when it is queried by hruid and language', async () => { | ||||
|         const result = await learningPathRepo.findByHruidAndLanguage(testLearningPath01.hruid, testLearningPath01.language as Language); | ||||
|         expect(result).toBeInstanceOf(LearningPath); | ||||
|         expectToBeCorrectEntity({ entity: result! }, { entity: examplePath }); | ||||
|         expectToBeCorrectEntity(result!, examplePath); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return null to a query on a non-existing hruid or language', async () => { | ||||
|  | @ -45,7 +31,7 @@ describe('LearningPathRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('should return the learning path when we search for a search term occurring in its title', async () => { | ||||
|         const result = await learningPathRepo.findByQueryStringAndLanguage(examplePath.title.slice(4, 9), examplePath.language); | ||||
|         const result = await learningPathRepo.findByQueryStringAndLanguage(examplePath.title.slice(9, 13), examplePath.language); | ||||
|         expectToHaveFoundPrecisely(examplePath, result); | ||||
|     }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import { getLearningPathRepository } from '../../../src/data/repositories'; | |||
| import { LearningPathRepository } from '../../../src/data/content/learning-path-repository'; | ||||
| import { setupTestApp } from '../../setup-tests'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import { testLearningPath01 } from '../../test_assets/content/learning-paths.testdata'; | ||||
| 
 | ||||
| describe('LearningPathRepository', () => { | ||||
|     let learningPathRepository: LearningPathRepository; | ||||
|  | @ -19,10 +20,10 @@ describe('LearningPathRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('should return requested learning path', async () => { | ||||
|         const learningPath = await learningPathRepository.findByHruidAndLanguage('id01', Language.English); | ||||
|         const learningPath = await learningPathRepository.findByHruidAndLanguage(testLearningPath01.hruid, testLearningPath01.language as Language); | ||||
| 
 | ||||
|         expect(learningPath).toBeTruthy(); | ||||
|         expect(learningPath?.title).toBe('repertoire Tool'); | ||||
|         expect(learningPath?.description).toBe('all about Tool'); | ||||
|         expect(learningPath?.title).toBe(testLearningPath01.title); | ||||
|         expect(learningPath?.description).toBe(testLearningPath01.description); | ||||
|     }); | ||||
| }); | ||||
|  |  | |||
|  | @ -38,8 +38,8 @@ describe('QuestionRepository', () => { | |||
|         const student = await studentRepository.findByUsername('Noordkaap'); | ||||
| 
 | ||||
|         const clazz = await getClassRepository().findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         const assignment = await getAssignmentRepository().findByClassAndId(clazz!, 1); | ||||
|         const group = await getGroupRepository().findByAssignmentAndGroupNumber(assignment!, 1); | ||||
|         const assignment = await getAssignmentRepository().findByClassAndId(clazz!, 21000); | ||||
|         const group = await getGroupRepository().findByAssignmentAndGroupNumber(assignment!, 21001); | ||||
|         await questionRepository.createQuestion({ | ||||
|             loId: id, | ||||
|             inGroup: group!, | ||||
|  | @ -57,7 +57,7 @@ describe('QuestionRepository', () => { | |||
|     let loId: LearningObjectIdentifier; | ||||
|     it('should find all questions for a certain learning object and assignment', async () => { | ||||
|         clazz = await getClassRepository().findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         assignment = await getAssignmentRepository().findByClassAndId(clazz!, 1); | ||||
|         assignment = await getAssignmentRepository().findByClassAndId(clazz!, 21000); | ||||
|         loId = { | ||||
|             hruid: 'id05', | ||||
|             language: Language.English, | ||||
|  |  | |||
|  | @ -1,37 +1,32 @@ | |||
| import { beforeAll, describe, expect, it } from 'vitest'; | ||||
| import { setupTestApp } from '../../setup-tests'; | ||||
| import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories'; | ||||
| import example from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | ||||
| import { LearningObject } from '../../../src/entities/content/learning-object.entity'; | ||||
| import databaseLearningObjectProvider from '../../../src/services/learning-objects/database-learning-object-provider'; | ||||
| import { expectToBeCorrectFilteredLearningObject } from '../../test-utils/expectations'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | ||||
| import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; | ||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||
| import { FilteredLearningObject } from '@dwengo-1/common/interfaces/learning-content'; | ||||
| 
 | ||||
| async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | ||||
|     const learningObjectRepo = getLearningObjectRepository(); | ||||
|     const learningPathRepo = getLearningPathRepository(); | ||||
|     const learningObject = learningObjectExample.createLearningObject(); | ||||
|     const learningPath = learningPathExample.createLearningPath(); | ||||
|     await learningObjectRepo.save(learningObject); | ||||
|     await learningPathRepo.save(learningPath); | ||||
|     return { learningObject, learningPath }; | ||||
| } | ||||
| import { FilteredLearningObject, LearningObjectNode, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||
| import { testPartiallyDatabaseAndPartiallyDwengoApiLearningPath } from '../../test_assets/content/learning-paths.testdata'; | ||||
| import { testLearningObjectPnNotebooks } from '../../test_assets/content/learning-objects.testdata'; | ||||
| import { LearningPath } from '@dwengo-1/common/dist/interfaces/learning-content'; | ||||
| import { RequiredEntityData } from '@mikro-orm/core'; | ||||
| import { getHtmlRenderingForTestLearningObject } from '../../test-utils/get-html-rendering'; | ||||
| 
 | ||||
| const EXPECTED_TITLE_FROM_DWENGO_LEARNING_OBJECT = 'Notebook opslaan'; | ||||
| 
 | ||||
| describe('DatabaseLearningObjectProvider', () => { | ||||
|     let exampleLearningObject: LearningObject; | ||||
|     let exampleLearningObject: RequiredEntityData<LearningObject>; | ||||
|     let exampleLearningPath: LearningPath; | ||||
|     let exampleLearningPathId: LearningPathIdentifier; | ||||
| 
 | ||||
|     beforeAll(async () => { | ||||
|         await setupTestApp(); | ||||
|         const exampleData = await initExampleData(); | ||||
|         exampleLearningObject = exampleData.learningObject; | ||||
|         exampleLearningPath = exampleData.learningPath; | ||||
|         exampleLearningObject = testLearningObjectPnNotebooks; | ||||
|         exampleLearningPath = testPartiallyDatabaseAndPartiallyDwengoApiLearningPath; | ||||
| 
 | ||||
|         exampleLearningPathId = { | ||||
|             hruid: exampleLearningPath.hruid, | ||||
|             language: exampleLearningPath.language as Language, | ||||
|         }; | ||||
|     }); | ||||
|     describe('getLearningObjectById', () => { | ||||
|         it('should return the learning object when it is queried by its id', async () => { | ||||
|  | @ -61,7 +56,7 @@ describe('DatabaseLearningObjectProvider', () => { | |||
|         it('should return the correct rendering of the learning object', async () => { | ||||
|             const result = await databaseLearningObjectProvider.getLearningObjectHTML(exampleLearningObject); | ||||
|             // Set newlines so your tests are platform-independent.
 | ||||
|             expect(result).toEqual(example.getHTMLRendering().replace(/\r\n/g, '\n')); | ||||
|             expect(result).toEqual(getHtmlRenderingForTestLearningObject(exampleLearningObject).replace(/\r\n/g, '\n')); | ||||
|         }); | ||||
|         it('should return null for a non-existing learning object', async () => { | ||||
|             const result = await databaseLearningObjectProvider.getLearningObjectHTML({ | ||||
|  | @ -73,8 +68,8 @@ describe('DatabaseLearningObjectProvider', () => { | |||
|     }); | ||||
|     describe('getLearningObjectIdsFromPath', () => { | ||||
|         it('should return all learning object IDs from a path', async () => { | ||||
|             const result = await databaseLearningObjectProvider.getLearningObjectIdsFromPath(exampleLearningPath); | ||||
|             expect(new Set(result)).toEqual(new Set(exampleLearningPath.nodes.map((it) => it.learningObjectHruid))); | ||||
|             const result = await databaseLearningObjectProvider.getLearningObjectIdsFromPath(exampleLearningPathId); | ||||
|             expect(new Set(result)).toEqual(new Set(exampleLearningPath.nodes.map((it: LearningObjectNode) => it.learningobject_hruid))); | ||||
|         }); | ||||
|         it('should throw an error if queried with a path identifier for which there is no learning path', async () => { | ||||
|             await expect( | ||||
|  | @ -89,9 +84,11 @@ describe('DatabaseLearningObjectProvider', () => { | |||
|     }); | ||||
|     describe('getLearningObjectsFromPath', () => { | ||||
|         it('should correctly return all learning objects which are on the path, even those who are not in the database', async () => { | ||||
|             const result = await databaseLearningObjectProvider.getLearningObjectsFromPath(exampleLearningPath); | ||||
|             const result = await databaseLearningObjectProvider.getLearningObjectsFromPath(exampleLearningPathId); | ||||
|             expect(result.length).toBe(exampleLearningPath.nodes.length); | ||||
|             expect(new Set(result.map((it) => it.key))).toEqual(new Set(exampleLearningPath.nodes.map((it) => it.learningObjectHruid))); | ||||
|             expect(new Set(result.map((it) => it.key))).toEqual( | ||||
|                 new Set(exampleLearningPath.nodes.map((it: LearningObjectNode) => it.learningobject_hruid)) | ||||
|             ); | ||||
| 
 | ||||
|             expect(result.map((it) => it.title)).toContainEqual(EXPECTED_TITLE_FROM_DWENGO_LEARNING_OBJECT); | ||||
|         }); | ||||
|  |  | |||
|  | @ -1,14 +1,14 @@ | |||
| import { beforeAll, describe, expect, it } from 'vitest'; | ||||
| import { setupTestApp } from '../../setup-tests'; | ||||
| import { LearningObject } from '../../../src/entities/content/learning-object.entity'; | ||||
| import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories'; | ||||
| import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | ||||
| import learningObjectService from '../../../src/services/learning-objects/learning-object-service'; | ||||
| import { envVars, getEnvVar } from '../../../src/util/envVars'; | ||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||
| import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; | ||||
| import { LearningObjectIdentifierDTO, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||
| import { LearningObjectIdentifierDTO, LearningPath as LearningPathDTO, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import { testLearningObjectPnNotebooks } from '../../test_assets/content/learning-objects.testdata'; | ||||
| import { testPartiallyDatabaseAndPartiallyDwengoApiLearningPath } from '../../test_assets/content/learning-paths.testdata'; | ||||
| import { RequiredEntityData } from '@mikro-orm/core'; | ||||
| import { getHtmlRenderingForTestLearningObject } from '../../test-utils/get-html-rendering'; | ||||
| 
 | ||||
| const EXPECTED_DWENGO_LEARNING_OBJECT_TITLE = 'Werken met notebooks'; | ||||
| const DWENGO_TEST_LEARNING_OBJECT_ID: LearningObjectIdentifierDTO = { | ||||
|  | @ -23,25 +23,20 @@ const DWENGO_TEST_LEARNING_PATH_ID: LearningPathIdentifier = { | |||
| }; | ||||
| const DWENGO_TEST_LEARNING_PATH_HRUIDS = new Set(['pn_werkingnotebooks', 'pn_werkingnotebooks2', 'pn_werkingnotebooks3']); | ||||
| 
 | ||||
| async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | ||||
|     const learningObjectRepo = getLearningObjectRepository(); | ||||
|     const learningPathRepo = getLearningPathRepository(); | ||||
|     const learningObject = learningObjectExample.createLearningObject(); | ||||
|     const learningPath = learningPathExample.createLearningPath(); | ||||
|     await learningObjectRepo.save(learningObject); | ||||
|     await learningPathRepo.save(learningPath); | ||||
|     return { learningObject, learningPath }; | ||||
| } | ||||
| 
 | ||||
| describe('LearningObjectService', () => { | ||||
|     let exampleLearningObject: LearningObject; | ||||
|     let exampleLearningPath: LearningPath; | ||||
|     let exampleLearningObject: RequiredEntityData<LearningObject>; | ||||
|     let exampleLearningPath: LearningPathDTO; | ||||
|     let exampleLearningPathId: LearningPathIdentifier; | ||||
| 
 | ||||
|     beforeAll(async () => { | ||||
|         await setupTestApp(); | ||||
|         const exampleData = await initExampleData(); | ||||
|         exampleLearningObject = exampleData.learningObject; | ||||
|         exampleLearningPath = exampleData.learningPath; | ||||
|         exampleLearningObject = testLearningObjectPnNotebooks; | ||||
|         exampleLearningPath = testPartiallyDatabaseAndPartiallyDwengoApiLearningPath; | ||||
| 
 | ||||
|         exampleLearningPathId = { | ||||
|             hruid: exampleLearningPath.hruid, | ||||
|             language: exampleLearningPath.language as Language, | ||||
|         }; | ||||
|     }); | ||||
| 
 | ||||
|     describe('getLearningObjectById', () => { | ||||
|  | @ -69,7 +64,7 @@ describe('LearningObjectService', () => { | |||
|             const result = await learningObjectService.getLearningObjectHTML(exampleLearningObject); | ||||
|             expect(result).not.toBeNull(); | ||||
|             // Set newlines so your tests are platform-independent.
 | ||||
|             expect(result).toEqual(learningObjectExample.getHTMLRendering().replace(/\r\n/g, '\n')); | ||||
|             expect(result).toEqual(getHtmlRenderingForTestLearningObject(exampleLearningObject).replace(/\r\n/g, '\n')); | ||||
|         }); | ||||
|         it( | ||||
|             'returns the same HTML as the Dwengo API when queried with the identifier of a learning object that does ' + | ||||
|  | @ -97,8 +92,8 @@ describe('LearningObjectService', () => { | |||
| 
 | ||||
|     describe('getLearningObjectsFromPath', () => { | ||||
|         it('returns all learning objects when a learning path in the database is queried', async () => { | ||||
|             const result = await learningObjectService.getLearningObjectsFromPath(exampleLearningPath); | ||||
|             expect(result.map((it) => it.key)).toEqual(exampleLearningPath.nodes.map((it) => it.learningObjectHruid)); | ||||
|             const result = await learningObjectService.getLearningObjectsFromPath(exampleLearningPathId); | ||||
|             expect(result.map((it) => it.key)).toEqual(exampleLearningPath.nodes.map((it) => it.learningobject_hruid)); | ||||
|         }); | ||||
|         it('also returns all learning objects when a learning path from the Dwengo API is queried', async () => { | ||||
|             const result = await learningObjectService.getLearningObjectsFromPath(DWENGO_TEST_LEARNING_PATH_ID); | ||||
|  | @ -115,8 +110,8 @@ describe('LearningObjectService', () => { | |||
| 
 | ||||
|     describe('getLearningObjectIdsFromPath', () => { | ||||
|         it('returns all learning objects when a learning path in the database is queried', async () => { | ||||
|             const result = await learningObjectService.getLearningObjectIdsFromPath(exampleLearningPath); | ||||
|             expect(result).toEqual(exampleLearningPath.nodes.map((it) => it.learningObjectHruid)); | ||||
|             const result = await learningObjectService.getLearningObjectIdsFromPath(exampleLearningPathId); | ||||
|             expect(result).toEqual(exampleLearningPath.nodes.map((it) => it.learningobject_hruid)); | ||||
|         }); | ||||
|         it('also returns all learning object hruids when a learning path from the Dwengo API is queried', async () => { | ||||
|             const result = await learningObjectService.getLearningObjectIdsFromPath(DWENGO_TEST_LEARNING_PATH_ID); | ||||
|  |  | |||
|  | @ -1,26 +1,35 @@ | |||
| import { describe, expect, it } from 'vitest'; | ||||
| import mdExample from '../../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | ||||
| import multipleChoiceExample from '../../../test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example'; | ||||
| import essayExample from '../../../test-assets/learning-objects/test-essay/test-essay-example'; | ||||
| import { beforeAll, describe, expect, it } from 'vitest'; | ||||
| import processingService from '../../../../src/services/learning-objects/processing/processing-service'; | ||||
| import { | ||||
|     testLearningObjectEssayQuestion, | ||||
|     testLearningObjectMultipleChoice, | ||||
|     testLearningObjectPnNotebooks, | ||||
| } from '../../../test_assets/content/learning-objects.testdata'; | ||||
| import { getHtmlRenderingForTestLearningObject } from '../../../test-utils/get-html-rendering'; | ||||
| import { getLearningObjectRepository } from '../../../../src/data/repositories'; | ||||
| import { setupTestApp } from '../../../setup-tests'; | ||||
| 
 | ||||
| describe('ProcessingService', () => { | ||||
|     beforeAll(async () => { | ||||
|         await setupTestApp(); | ||||
|     }); | ||||
| 
 | ||||
|     it('renders a markdown learning object correctly', async () => { | ||||
|         const markdownLearningObject = mdExample.createLearningObject(); | ||||
|         const markdownLearningObject = getLearningObjectRepository().create(testLearningObjectPnNotebooks); | ||||
|         const result = await processingService.render(markdownLearningObject); | ||||
|         // Set newlines so your tests are platform-independent.
 | ||||
|         expect(result).toEqual(mdExample.getHTMLRendering().replace(/\r\n/g, '\n')); | ||||
|         expect(result).toEqual(getHtmlRenderingForTestLearningObject(markdownLearningObject).replace(/\r\n/g, '\n')); | ||||
|     }); | ||||
| 
 | ||||
|     it('renders a multiple choice question correctly', async () => { | ||||
|         const multipleChoiceLearningObject = multipleChoiceExample.createLearningObject(); | ||||
|         const result = await processingService.render(multipleChoiceLearningObject); | ||||
|         expect(result).toEqual(multipleChoiceExample.getHTMLRendering().replace(/\r\n/g, '\n')); | ||||
|         const testLearningObject = getLearningObjectRepository().create(testLearningObjectMultipleChoice); | ||||
|         const result = await processingService.render(testLearningObject); | ||||
|         expect(result).toEqual(getHtmlRenderingForTestLearningObject(testLearningObjectMultipleChoice).replace(/\r\n/g, '\n')); | ||||
|     }); | ||||
| 
 | ||||
|     it('renders an essay question correctly', async () => { | ||||
|         const essayLearningObject = essayExample.createLearningObject(); | ||||
|         const essayLearningObject = getLearningObjectRepository().create(testLearningObjectEssayQuestion); | ||||
|         const result = await processingService.render(essayLearningObject); | ||||
|         expect(result).toEqual(essayExample.getHTMLRendering().replace(/\r\n/g, '\n')); | ||||
|         expect(result).toEqual(getHtmlRenderingForTestLearningObject(essayLearningObject).replace(/\r\n/g, '\n')); | ||||
|     }); | ||||
| }); | ||||
|  |  | |||
|  | @ -2,227 +2,112 @@ import { beforeAll, describe, expect, it } from 'vitest'; | |||
| import { LearningObject } from '../../../src/entities/content/learning-object.entity.js'; | ||||
| import { setupTestApp } from '../../setup-tests.js'; | ||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity.js'; | ||||
| import { | ||||
|     getAssignmentRepository, | ||||
|     getClassRepository, | ||||
|     getGroupRepository, | ||||
|     getLearningObjectRepository, | ||||
|     getLearningPathRepository, | ||||
|     getStudentRepository, | ||||
|     getSubmissionRepository, | ||||
| } from '../../../src/data/repositories.js'; | ||||
| import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.js'; | ||||
| import learningPathExample from '../../test-assets/learning-paths/pn-werking-example.js'; | ||||
| import { getSubmissionRepository } from '../../../src/data/repositories.js'; | ||||
| 
 | ||||
| import databaseLearningPathProvider from '../../../src/services/learning-paths/database-learning-path-provider.js'; | ||||
| import { expectToBeCorrectLearningPath } from '../../test-utils/expectations.js'; | ||||
| import learningObjectService from '../../../src/services/learning-objects/learning-object-service.js'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import { | ||||
|     ConditionTestLearningPathAndLearningObjects, | ||||
|     createConditionTestLearningPathAndLearningObjects, | ||||
| } from '../../test-assets/learning-paths/test-conditions-example.js'; | ||||
| import { Student } from '../../../src/entities/users/student.entity.js'; | ||||
| 
 | ||||
| import { LearningObjectNode, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; | ||||
| import { | ||||
|     testLearningObject01, | ||||
|     testLearningObjectEssayQuestion, | ||||
|     testLearningObjectMultipleChoice, | ||||
| } from '../../test_assets/content/learning-objects.testdata'; | ||||
| import { testLearningPathWithConditions } from '../../test_assets/content/learning-paths.testdata'; | ||||
| import { mapToLearningPath } from '../../../src/services/learning-paths/learning-path-service'; | ||||
| import { getTestGroup01, getTestGroup02 } from '../../test_assets/assignments/groups.testdata'; | ||||
| import { Group } from '../../../src/entities/assignments/group.entity.js'; | ||||
| import { RequiredEntityData } from '@mikro-orm/core'; | ||||
| 
 | ||||
| const STUDENT_A_USERNAME = 'student_a'; | ||||
| const STUDENT_B_USERNAME = 'student_b'; | ||||
| const CLASS_NAME = 'test_class'; | ||||
| 
 | ||||
| async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | ||||
|     const learningObjectRepo = getLearningObjectRepository(); | ||||
|     const learningPathRepo = getLearningPathRepository(); | ||||
|     const learningObject = learningObjectExample.createLearningObject(); | ||||
|     const learningPath = learningPathExample.createLearningPath(); | ||||
|     await learningObjectRepo.save(learningObject); | ||||
|     await learningPathRepo.save(learningPath); | ||||
|     return { learningObject, learningPath }; | ||||
| } | ||||
| 
 | ||||
| async function initPersonalizationTestData(): Promise<{ | ||||
|     learningContent: ConditionTestLearningPathAndLearningObjects; | ||||
|     studentA: Student; | ||||
|     studentB: Student; | ||||
| }> { | ||||
|     const studentRepo = getStudentRepository(); | ||||
|     const classRepo = getClassRepository(); | ||||
|     const assignmentRepo = getAssignmentRepository(); | ||||
|     const groupRepo = getGroupRepository(); | ||||
|     const submissionRepo = getSubmissionRepository(); | ||||
|     const learningPathRepo = getLearningPathRepository(); | ||||
|     const learningObjectRepo = getLearningObjectRepository(); | ||||
|     const learningContent = createConditionTestLearningPathAndLearningObjects(); | ||||
|     await learningObjectRepo.save(learningContent.branchingObject); | ||||
|     await learningObjectRepo.save(learningContent.finalObject); | ||||
|     await learningObjectRepo.save(learningContent.extraExerciseObject); | ||||
|     await learningPathRepo.save(learningContent.learningPath); | ||||
| 
 | ||||
|     // Create students
 | ||||
|     const studentA = studentRepo.create({ | ||||
|         username: STUDENT_A_USERNAME, | ||||
|         firstName: 'Aron', | ||||
|         lastName: 'Student', | ||||
|     }); | ||||
|     await studentRepo.save(studentA); | ||||
| 
 | ||||
|     const studentB = studentRepo.create({ | ||||
|         username: STUDENT_B_USERNAME, | ||||
|         firstName: 'Bill', | ||||
|         lastName: 'Student', | ||||
|     }); | ||||
|     await studentRepo.save(studentB); | ||||
| 
 | ||||
|     // Create class for students
 | ||||
|     const testClass = classRepo.create({ | ||||
|         classId: CLASS_NAME, | ||||
|         displayName: 'Test class', | ||||
|     }); | ||||
|     await classRepo.save(testClass); | ||||
| 
 | ||||
|     // Create assignment for students and assign them to different groups
 | ||||
|     const assignment = assignmentRepo.create({ | ||||
|         id: 0, | ||||
|         title: 'Test assignment', | ||||
|         description: 'Test description', | ||||
|         learningPathHruid: learningContent.learningPath.hruid, | ||||
|         learningPathLanguage: learningContent.learningPath.language, | ||||
|         within: testClass, | ||||
|     }); | ||||
| 
 | ||||
|     const groupA = groupRepo.create({ | ||||
|         groupNumber: 0, | ||||
|         members: [studentA], | ||||
|         assignment, | ||||
|     }); | ||||
|     await groupRepo.save(groupA); | ||||
| 
 | ||||
|     const groupB = groupRepo.create({ | ||||
|         groupNumber: 1, | ||||
|         members: [studentB], | ||||
|         assignment, | ||||
|     }); | ||||
|     await groupRepo.save(groupB); | ||||
| 
 | ||||
|     // Let each of the students make a submission in his own group.
 | ||||
|     const submissionA = submissionRepo.create({ | ||||
|         learningObjectHruid: learningContent.branchingObject.hruid, | ||||
|         learningObjectLanguage: learningContent.branchingObject.language, | ||||
|         learningObjectVersion: learningContent.branchingObject.version, | ||||
|         onBehalfOf: groupA, | ||||
|         submitter: studentA, | ||||
|         submissionTime: new Date(), | ||||
|         content: '[0]', | ||||
|     }); | ||||
|     await submissionRepo.save(submissionA); | ||||
| 
 | ||||
|     const submissionB = submissionRepo.create({ | ||||
|         learningObjectHruid: learningContent.branchingObject.hruid, | ||||
|         learningObjectLanguage: learningContent.branchingObject.language, | ||||
|         learningObjectVersion: learningContent.branchingObject.version, | ||||
|         onBehalfOf: groupA, | ||||
|         submitter: studentB, | ||||
|         submissionTime: new Date(), | ||||
|         content: '[1]', | ||||
|     }); | ||||
|     await submissionRepo.save(submissionB); | ||||
| 
 | ||||
|     return { | ||||
|         learningContent: learningContent, | ||||
|         studentA: studentA, | ||||
|         studentB: studentB, | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| function expectBranchingObjectNode( | ||||
|     result: LearningPathResponse, | ||||
|     persTestData: { | ||||
|         learningContent: ConditionTestLearningPathAndLearningObjects; | ||||
|         studentA: Student; | ||||
|         studentB: Student; | ||||
|     } | ||||
| ): LearningObjectNode { | ||||
|     const branchingObjectMatches = result.data![0].nodes.filter( | ||||
|         (it) => it.learningobject_hruid === persTestData.learningContent.branchingObject.hruid | ||||
|     ); | ||||
| function expectBranchingObjectNode(result: LearningPathResponse): LearningObjectNode { | ||||
|     const branchingObjectMatches = result.data![0].nodes.filter((it) => it.learningobject_hruid === testLearningObjectMultipleChoice.hruid); | ||||
|     expect(branchingObjectMatches.length).toBe(1); | ||||
|     return branchingObjectMatches[0]; | ||||
| } | ||||
| 
 | ||||
| describe('DatabaseLearningPathProvider', () => { | ||||
|     let example: { learningObject: LearningObject; learningPath: LearningPath }; | ||||
|     let persTestData: { learningContent: ConditionTestLearningPathAndLearningObjects; studentA: Student; studentB: Student }; | ||||
|     let testLearningPath: LearningPath; | ||||
|     let branchingLearningObject: RequiredEntityData<LearningObject>; | ||||
|     let extraExerciseLearningObject: RequiredEntityData<LearningObject>; | ||||
|     let finalLearningObject: RequiredEntityData<LearningObject>; | ||||
|     let groupA: Group; | ||||
|     let groupB: Group; | ||||
| 
 | ||||
|     beforeAll(async () => { | ||||
|         await setupTestApp(); | ||||
|         example = await initExampleData(); | ||||
|         persTestData = await initPersonalizationTestData(); | ||||
|         testLearningPath = mapToLearningPath(testLearningPathWithConditions, []); | ||||
|         branchingLearningObject = testLearningObjectMultipleChoice; | ||||
|         extraExerciseLearningObject = testLearningObject01; | ||||
|         finalLearningObject = testLearningObjectEssayQuestion; | ||||
|         groupA = getTestGroup01(); | ||||
|         groupB = getTestGroup02(); | ||||
| 
 | ||||
|         // Place different submissions for group A and B.
 | ||||
|         const submissionRepo = getSubmissionRepository(); | ||||
|         const submissionA = submissionRepo.create({ | ||||
|             learningObjectHruid: branchingLearningObject.hruid, | ||||
|             learningObjectLanguage: branchingLearningObject.language, | ||||
|             learningObjectVersion: branchingLearningObject.version, | ||||
|             content: '[0]', | ||||
|             onBehalfOf: groupA, | ||||
|             submissionTime: new Date(), | ||||
|             submitter: groupA.members[0], | ||||
|         }); | ||||
|         await submissionRepo.save(submissionA); | ||||
| 
 | ||||
|         const submissionB = submissionRepo.create({ | ||||
|             learningObjectHruid: branchingLearningObject.hruid, | ||||
|             learningObjectLanguage: branchingLearningObject.language, | ||||
|             learningObjectVersion: branchingLearningObject.version, | ||||
|             content: '[1]', | ||||
|             onBehalfOf: groupB, | ||||
|             submissionTime: new Date(), | ||||
|             submitter: groupB.members[0], | ||||
|         }); | ||||
|         await submissionRepo.save(submissionB); | ||||
|     }); | ||||
| 
 | ||||
|     describe('fetchLearningPaths', () => { | ||||
|         it('returns the learning path correctly', async () => { | ||||
|             const result = await databaseLearningPathProvider.fetchLearningPaths( | ||||
|                 [example.learningPath.hruid], | ||||
|                 example.learningPath.language, | ||||
|                 'the source' | ||||
|             ); | ||||
|             const result = await databaseLearningPathProvider.fetchLearningPaths([testLearningPath.hruid], testLearningPath.language, 'the source'); | ||||
|             expect(result.success).toBe(true); | ||||
|             expect(result.data?.length).toBe(1); | ||||
| 
 | ||||
|             const learningObjectsOnPath = ( | ||||
|                 await Promise.all( | ||||
|                     example.learningPath.nodes.map(async (node) => | ||||
|                         learningObjectService.getLearningObjectById({ | ||||
|                             hruid: node.learningObjectHruid, | ||||
|                             version: node.version, | ||||
|                             language: node.language, | ||||
|                         }) | ||||
|                     ) | ||||
|                 ) | ||||
|             ).filter((it) => it !== null); | ||||
| 
 | ||||
|             expectToBeCorrectLearningPath(result.data![0], example.learningPath, learningObjectsOnPath); | ||||
|             expectToBeCorrectLearningPath(result.data![0], testLearningPathWithConditions); | ||||
|         }); | ||||
|         it('returns the correct personalized learning path', async () => { | ||||
|             // For student A:
 | ||||
|             let result = await databaseLearningPathProvider.fetchLearningPaths( | ||||
|                 [persTestData.learningContent.learningPath.hruid], | ||||
|                 persTestData.learningContent.learningPath.language, | ||||
|                 [testLearningPath.hruid], | ||||
|                 testLearningPath.language, | ||||
|                 'the source', | ||||
|                 { type: 'student', student: persTestData.studentA } | ||||
|                 groupA | ||||
|             ); | ||||
|             expect(result.success).toBeTruthy(); | ||||
|             expect(result.data?.length).toBe(1); | ||||
| 
 | ||||
|             // There should be exactly one branching object
 | ||||
|             let branchingObject = expectBranchingObjectNode(result, persTestData); | ||||
|             let branchingObject = expectBranchingObjectNode(result); | ||||
| 
 | ||||
|             expect(branchingObject.transitions.filter((it) => it.next.hruid === persTestData.learningContent.finalObject.hruid).length).toBe(0); // StudentA picked the first option, therefore, there should be no direct path to the final object.
 | ||||
|             expect(branchingObject.transitions.filter((it) => it.next.hruid === persTestData.learningContent.extraExerciseObject.hruid).length).toBe( | ||||
|                 1 | ||||
|             ); // There should however be a path to the extra exercise object.
 | ||||
|             expect(branchingObject.transitions.filter((it) => it.next.hruid === finalLearningObject.hruid).length).toBe(0); // StudentA picked the first option, therefore, there should be no direct path to the final object.
 | ||||
|             expect(branchingObject.transitions.filter((it) => it.next.hruid === extraExerciseLearningObject.hruid).length).toBe(1); // There should however be a path to the extra exercise object.
 | ||||
| 
 | ||||
|             // For student B:
 | ||||
|             result = await databaseLearningPathProvider.fetchLearningPaths( | ||||
|                 [persTestData.learningContent.learningPath.hruid], | ||||
|                 persTestData.learningContent.learningPath.language, | ||||
|                 'the source', | ||||
|                 { type: 'student', student: persTestData.studentB } | ||||
|             ); | ||||
|             result = await databaseLearningPathProvider.fetchLearningPaths([testLearningPath.hruid], testLearningPath.language, 'the source', groupB); | ||||
|             expect(result.success).toBeTruthy(); | ||||
|             expect(result.data?.length).toBe(1); | ||||
| 
 | ||||
|             // There should still be exactly one branching object
 | ||||
|             branchingObject = expectBranchingObjectNode(result, persTestData); | ||||
|             branchingObject = expectBranchingObjectNode(result); | ||||
| 
 | ||||
|             // However, now the student picks the other option.
 | ||||
|             expect(branchingObject.transitions.filter((it) => it.next.hruid === persTestData.learningContent.finalObject.hruid).length).toBe(1); // StudentB picked the second option, therefore, there should be a direct path to the final object.
 | ||||
|             expect(branchingObject.transitions.filter((it) => it.next.hruid === persTestData.learningContent.extraExerciseObject.hruid).length).toBe( | ||||
|                 0 | ||||
|             ); // There should not be a path anymore to the extra exercise object.
 | ||||
|             expect(branchingObject.transitions.filter((it) => it.next.hruid === finalLearningObject.hruid).length).toBe(1); // StudentB picked the second option, therefore, there should be a direct path to the final object.
 | ||||
|             expect(branchingObject.transitions.filter((it) => it.next.hruid === extraExerciseLearningObject.hruid).length).toBe(0); // There should not be a path anymore to the extra exercise object.
 | ||||
|         }); | ||||
|         it('returns a non-successful response if a non-existing learning path is queried', async () => { | ||||
|             const result = await databaseLearningPathProvider.fetchLearningPaths( | ||||
|                 [example.learningPath.hruid], | ||||
|                 [testLearningPath.hruid], | ||||
|                 Language.Abkhazian, // Wrong language
 | ||||
|                 'the source' | ||||
|             ); | ||||
|  | @ -233,27 +118,24 @@ describe('DatabaseLearningPathProvider', () => { | |||
| 
 | ||||
|     describe('searchLearningPaths', () => { | ||||
|         it('returns the correct learning path when queried with a substring of its title', async () => { | ||||
|             const result = await databaseLearningPathProvider.searchLearningPaths( | ||||
|                 example.learningPath.title.substring(2, 6), | ||||
|                 example.learningPath.language | ||||
|             ); | ||||
|             const result = await databaseLearningPathProvider.searchLearningPaths(testLearningPath.title.substring(2, 6), testLearningPath.language); | ||||
|             expect(result.length).toBe(1); | ||||
|             expect(result[0].title).toBe(example.learningPath.title); | ||||
|             expect(result[0].description).toBe(example.learningPath.description); | ||||
|             expect(result[0].title).toBe(testLearningPath.title); | ||||
|             expect(result[0].description).toBe(testLearningPath.description); | ||||
|         }); | ||||
|         it('returns the correct learning path when queried with a substring of the description', async () => { | ||||
|             const result = await databaseLearningPathProvider.searchLearningPaths( | ||||
|                 example.learningPath.description.substring(5, 12), | ||||
|                 example.learningPath.language | ||||
|                 testLearningPath.description.substring(5, 12), | ||||
|                 testLearningPath.language | ||||
|             ); | ||||
|             expect(result.length).toBe(1); | ||||
|             expect(result[0].title).toBe(example.learningPath.title); | ||||
|             expect(result[0].description).toBe(example.learningPath.description); | ||||
|             expect(result[0].title).toBe(testLearningPath.title); | ||||
|             expect(result[0].description).toBe(testLearningPath.description); | ||||
|         }); | ||||
|         it('returns an empty result when queried with a text which is not a substring of the title or the description of a learning path', async () => { | ||||
|             const result = await databaseLearningPathProvider.searchLearningPaths( | ||||
|                 'substring which does not occur in the title or the description of a learning object', | ||||
|                 example.learningPath.language | ||||
|                 testLearningPath.language | ||||
|             ); | ||||
|             expect(result.length).toBe(0); | ||||
|         }); | ||||
|  |  | |||
|  | @ -1,22 +1,9 @@ | |||
| import { beforeAll, describe, expect, it } from 'vitest'; | ||||
| import { setupTestApp } from '../../setup-tests'; | ||||
| import { LearningObject } from '../../../src/entities/content/learning-object.entity'; | ||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||
| import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories'; | ||||
| import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | ||||
| import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; | ||||
| import learningPathService from '../../../src/services/learning-paths/learning-path-service'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| 
 | ||||
| async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | ||||
|     const learningObjectRepo = getLearningObjectRepository(); | ||||
|     const learningPathRepo = getLearningPathRepository(); | ||||
|     const learningObject = learningObjectExample.createLearningObject(); | ||||
|     const learningPath = learningPathExample.createLearningPath(); | ||||
|     await learningObjectRepo.save(learningObject); | ||||
|     await learningPathRepo.save(learningPath); | ||||
|     return { learningObject, learningPath }; | ||||
| } | ||||
| import { testPartiallyDatabaseAndPartiallyDwengoApiLearningPath } from '../../test_assets/content/learning-paths.testdata'; | ||||
| import { LearningPath as LearningPathDTO } from '@dwengo-1/common/interfaces/learning-content'; | ||||
| 
 | ||||
| const TEST_DWENGO_LEARNING_PATH_HRUID = 'pn_werking'; | ||||
| const TEST_DWENGO_LEARNING_PATH_TITLE = 'Werken met notebooks'; | ||||
|  | @ -24,42 +11,49 @@ const TEST_DWENGO_EXCLUSIVE_LEARNING_PATH_SEARCH_QUERY = 'Microscopie'; | |||
| const TEST_SEARCH_QUERY_EXPECTING_NO_MATCHES = 'su$m8f9usf89ud<p9<U8SDP8UP9'; | ||||
| 
 | ||||
| describe('LearningPathService', () => { | ||||
|     let example: { learningObject: LearningObject; learningPath: LearningPath }; | ||||
|     let testLearningPath: LearningPathDTO; | ||||
|     beforeAll(async () => { | ||||
|         await setupTestApp(); | ||||
|         example = await initExampleData(); | ||||
|         testLearningPath = testPartiallyDatabaseAndPartiallyDwengoApiLearningPath; | ||||
|     }); | ||||
|     describe('fetchLearningPaths', () => { | ||||
|         it('should return learning paths both from the database and from the Dwengo API', async () => { | ||||
|             const result = await learningPathService.fetchLearningPaths( | ||||
|                 [example.learningPath.hruid, TEST_DWENGO_LEARNING_PATH_HRUID], | ||||
|                 example.learningPath.language, | ||||
|                 [testLearningPath.hruid, TEST_DWENGO_LEARNING_PATH_HRUID], | ||||
|                 testLearningPath.language as Language, | ||||
|                 'the source' | ||||
|             ); | ||||
|             expect(result.success).toBeTruthy(); | ||||
|             expect(result.data?.filter((it) => it.hruid === TEST_DWENGO_LEARNING_PATH_HRUID).length).not.toBe(0); | ||||
|             expect(result.data?.filter((it) => it.hruid === example.learningPath.hruid).length).not.toBe(0); | ||||
|             expect(result.data?.filter((it) => it.hruid === testLearningPath.hruid).length).not.toBe(0); | ||||
|             expect(result.data?.find((it) => it.hruid === TEST_DWENGO_LEARNING_PATH_HRUID)?.title).toEqual(TEST_DWENGO_LEARNING_PATH_TITLE); | ||||
|             expect(result.data?.find((it) => it.hruid === example.learningPath.hruid)?.title).toEqual(example.learningPath.title); | ||||
|             expect(result.data?.find((it) => it.hruid === testLearningPath.hruid)?.title).toEqual(testLearningPath.title); | ||||
|         }); | ||||
|         it('should include both the learning objects from the Dwengo API and learning objects from the database in its response', async () => { | ||||
|             const result = await learningPathService.fetchLearningPaths([example.learningPath.hruid], example.learningPath.language, 'the source'); | ||||
|             const result = await learningPathService.fetchLearningPaths( | ||||
|                 [testLearningPath.hruid], | ||||
|                 testLearningPath.language as Language, | ||||
|                 'the source' | ||||
|             ); | ||||
|             expect(result.success).toBeTruthy(); | ||||
|             expect(result.data?.length).toBe(1); | ||||
| 
 | ||||
|             // Should include all the nodes, even those pointing to foreign learning objects.
 | ||||
|             expect([...result.data![0].nodes.map((it) => it.learningobject_hruid)].sort((a, b) => a.localeCompare(b))).toEqual( | ||||
|                 example.learningPath.nodes.map((it) => it.learningObjectHruid).sort((a, b) => a.localeCompare(b)) | ||||
|                 testLearningPath.nodes.map((it) => it.learningobject_hruid).sort((a, b) => a.localeCompare(b)) | ||||
|             ); | ||||
|         }); | ||||
|     }); | ||||
|     describe('searchLearningPath', () => { | ||||
|         it('should include both the learning paths from the Dwengo API and those from the database in its response', async () => { | ||||
|             // This matches the learning object in the database, but definitely also some learning objects in the Dwengo API.
 | ||||
|             const result = await learningPathService.searchLearningPaths(example.learningPath.title.substring(2, 3), example.learningPath.language); | ||||
|             const result = await learningPathService.searchLearningPaths( | ||||
|                 testLearningPath.title.substring(2, 3), | ||||
|                 testLearningPath.language as Language | ||||
|             ); | ||||
| 
 | ||||
|             // Should find the one from the database
 | ||||
|             expect(result.filter((it) => it.hruid === example.learningPath.hruid && it.title === example.learningPath.title).length).toBe(1); | ||||
|             expect(result.filter((it) => it.hruid === testLearningPath.hruid && it.title === testLearningPath.title).length).toBe(1); | ||||
| 
 | ||||
|             // But should not only find that one.
 | ||||
|             expect(result.length).not.toBeLessThan(2); | ||||
|  | @ -71,7 +65,7 @@ describe('LearningPathService', () => { | |||
|             expect(result.length).not.toBe(0); | ||||
| 
 | ||||
|             // But not the example learning path.
 | ||||
|             expect(result.filter((it) => it.hruid === example.learningPath.hruid && it.title === example.learningPath.title).length).toBe(0); | ||||
|             expect(result.filter((it) => it.hruid === testLearningPath.hruid && it.title === testLearningPath.title).length).toBe(0); | ||||
|         }); | ||||
|         it('should return an empty list if neither the Dwengo API nor the database contains matches', async () => { | ||||
|             const result = await learningPathService.searchLearningPaths(TEST_SEARCH_QUERY_EXPECTING_NO_MATCHES, Language.Dutch); | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ import { makeTestQuestions } from './test_assets/questions/questions.testdata.js | |||
| import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; | ||||
| import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; | ||||
| import { Collection } from '@mikro-orm/core'; | ||||
| import { Group } from '../src/entities/assignments/group.entity'; | ||||
| 
 | ||||
| export async function setupTestApp(): Promise<void> { | ||||
|     dotenv.config({ path: '.env.test' }); | ||||
|  | @ -29,8 +30,8 @@ export async function setupTestApp(): Promise<void> { | |||
|     const assignments = makeTestAssignemnts(em, classes); | ||||
|     const groups = makeTestGroups(em, students, assignments); | ||||
| 
 | ||||
|     assignments[0].groups = new Collection(groups.slice(0, 3)); | ||||
|     assignments[1].groups = new Collection(groups.slice(3, 4)); | ||||
|     assignments[0].groups = new Collection<Group>(groups.slice(0, 3)); | ||||
|     assignments[1].groups = new Collection<Group>(groups.slice(3, 4)); | ||||
| 
 | ||||
|     const teacherInvitations = makeTestTeacherInvitations(em, teachers, classes); | ||||
|     const classJoinRequests = makeTestClassJoinRequests(em, students, classes); | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
|  | @ -1,32 +0,0 @@ | |||
| import { LearningObjectExample } from '../learning-object-example'; | ||||
| import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import { loadTestAsset } from '../../../test-utils/load-test-asset'; | ||||
| import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | ||||
| 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 | ||||
|  * on a path), but where the precise contents of the learning object are not important. | ||||
|  */ | ||||
| export function dummyLearningObject(hruid: string, language: Language, title: string): LearningObjectExample { | ||||
|     return { | ||||
|         createLearningObject: (): LearningObject => { | ||||
|             const learningObject = new LearningObject(); | ||||
|             learningObject.hruid = getEnvVar(envVars.UserContentPrefix) + hruid; | ||||
|             learningObject.language = language; | ||||
|             learningObject.version = 1; | ||||
|             learningObject.title = title; | ||||
|             learningObject.description = 'Just a dummy learning object for testing purposes'; | ||||
|             learningObject.contentType = DwengoContentType.TEXT_PLAIN; | ||||
|             learningObject.content = Buffer.from('Dummy content'); | ||||
|             learningObject.returnValue = { | ||||
|                 callbackUrl: `/learningObject/${hruid}/submissions`, | ||||
|                 callbackSchema: '[]', | ||||
|             }; | ||||
|             return learningObject; | ||||
|         }, | ||||
|         createAttachment: {}, | ||||
|         getHTMLRendering: () => loadTestAsset('learning-objects/dummy/rendering.txt').toString(), | ||||
|     }; | ||||
| } | ||||
|  | @ -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; | ||||
| } | ||||
|  | @ -1,74 +0,0 @@ | |||
| import { LearningObjectExample } from '../learning-object-example'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | ||||
| import { loadTestAsset } from '../../../test-utils/load-test-asset'; | ||||
| import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | ||||
| import { Attachment } from '../../../../src/entities/content/attachment.entity'; | ||||
| import { envVars, getEnvVar } from '../../../../src/util/envVars'; | ||||
| import { EducationalGoal } from '../../../../src/entities/content/educational-goal.entity'; | ||||
| import { ReturnValue } from '../../../../src/entities/content/return-value.entity'; | ||||
| 
 | ||||
| const ASSETS_PREFIX = 'learning-objects/pn-werkingnotebooks/'; | ||||
| 
 | ||||
| const example: LearningObjectExample = { | ||||
|     createLearningObject: () => { | ||||
|         const learningObject = new LearningObject(); | ||||
|         learningObject.hruid = `${getEnvVar(envVars.UserContentPrefix)}pn_werkingnotebooks`; | ||||
|         learningObject.version = 3; | ||||
|         learningObject.language = Language.Dutch; | ||||
|         learningObject.title = 'Werken met notebooks'; | ||||
|         learningObject.description = 'Leren werken met notebooks'; | ||||
|         learningObject.keywords = ['Python', 'KIKS', 'Wiskunde', 'STEM', 'AI']; | ||||
| 
 | ||||
|         const educationalGoal1 = new EducationalGoal(); | ||||
|         educationalGoal1.source = 'Source'; | ||||
|         educationalGoal1.id = 'id'; | ||||
| 
 | ||||
|         const educationalGoal2 = new EducationalGoal(); | ||||
|         educationalGoal2.source = 'Source2'; | ||||
|         educationalGoal2.id = 'id2'; | ||||
| 
 | ||||
|         learningObject.educationalGoals = [educationalGoal1, educationalGoal2]; | ||||
|         learningObject.admins = []; | ||||
|         learningObject.contentType = DwengoContentType.TEXT_MARKDOWN; | ||||
|         learningObject.teacherExclusive = false; | ||||
|         learningObject.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', | ||||
|         ]; | ||||
|         learningObject.copyright = 'dwengo'; | ||||
|         learningObject.license = 'dwengo'; | ||||
|         learningObject.estimatedTime = 10; | ||||
| 
 | ||||
|         const returnValue = new ReturnValue(); | ||||
|         returnValue.callbackUrl = 'callback_url_example'; | ||||
|         returnValue.callbackSchema = '{"att": "test", "att2": "test2"}'; | ||||
| 
 | ||||
|         learningObject.returnValue = returnValue; | ||||
|         learningObject.available = true; | ||||
|         learningObject.content = loadTestAsset(`${ASSETS_PREFIX}/content.md`); | ||||
| 
 | ||||
|         return learningObject; | ||||
|     }, | ||||
|     createAttachment: { | ||||
|         dwengoLogo: (learningObject) => { | ||||
|             const att = new Attachment(); | ||||
|             att.learningObject = learningObject; | ||||
|             att.name = 'dwengo.png'; | ||||
|             att.mimeType = 'image/png'; | ||||
|             att.content = loadTestAsset(`${ASSETS_PREFIX}/dwengo.png`); | ||||
|             return att; | ||||
|         }, | ||||
|         knop: (learningObject) => { | ||||
|             const att = new Attachment(); | ||||
|             att.learningObject = learningObject; | ||||
|             att.name = 'Knop.png'; | ||||
|             att.mimeType = 'image/png'; | ||||
|             att.content = loadTestAsset(`${ASSETS_PREFIX}/Knop.png`); | ||||
|             return att; | ||||
|         }, | ||||
|     }, | ||||
|     getHTMLRendering: () => loadTestAsset(`${ASSETS_PREFIX}/rendering.txt`).toString(), | ||||
| }; | ||||
| export default example; | ||||
|  | @ -1,2 +0,0 @@ | |||
| ::MC basic:: | ||||
| How are you? {} | ||||
|  | @ -1,7 +0,0 @@ | |||
| <div class="learning-object-gift"> | ||||
|     <div id="gift-q1" class="gift-question"> | ||||
|         <h2 id="gift-q1-title" class="gift-title">MC basic</h2> | ||||
|         <p id="gift-q1-stem" class="gift-stem">How are you?</p> | ||||
|         <textarea id="gift-q1-answer" class="gift-essay-answer"></textarea> | ||||
|     </div> | ||||
| </div> | ||||
|  | @ -1,28 +0,0 @@ | |||
| import { LearningObjectExample } from '../learning-object-example'; | ||||
| import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | ||||
| import { loadTestAsset } from '../../../test-utils/load-test-asset'; | ||||
| import { envVars, getEnvVar } from '../../../../src/util/envVars'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | ||||
| 
 | ||||
| const example: LearningObjectExample = { | ||||
|     createLearningObject: () => { | ||||
|         const learningObject = new LearningObject(); | ||||
|         learningObject.hruid = `${getEnvVar(envVars.UserContentPrefix)}test_essay`; | ||||
|         learningObject.language = Language.English; | ||||
|         learningObject.version = 1; | ||||
|         learningObject.title = 'Essay question for testing'; | ||||
|         learningObject.description = 'This essay question was only created for testing purposes.'; | ||||
|         learningObject.contentType = DwengoContentType.GIFT; | ||||
|         learningObject.returnValue = { | ||||
|             callbackUrl: `/learningObject/${learningObject.hruid}/submissions`, | ||||
|             callbackSchema: '["antwoord vraag 1"]', | ||||
|         }; | ||||
|         learningObject.content = loadTestAsset('learning-objects/test-essay/content.txt'); | ||||
|         return learningObject; | ||||
|     }, | ||||
|     createAttachment: {}, | ||||
|     getHTMLRendering: () => loadTestAsset('learning-objects/test-essay/rendering.txt').toString(), | ||||
| }; | ||||
| 
 | ||||
| export default example; | ||||
|  | @ -1,28 +0,0 @@ | |||
| import { LearningObjectExample } from '../learning-object-example'; | ||||
| import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | ||||
| import { loadTestAsset } from '../../../test-utils/load-test-asset'; | ||||
| import { envVars, getEnvVar } from '../../../../src/util/envVars'; | ||||
| import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| 
 | ||||
| const example: LearningObjectExample = { | ||||
|     createLearningObject: () => { | ||||
|         const learningObject = new LearningObject(); | ||||
|         learningObject.hruid = `${getEnvVar(envVars.UserContentPrefix)}test_multiple_choice`; | ||||
|         learningObject.language = Language.English; | ||||
|         learningObject.version = 1; | ||||
|         learningObject.title = 'Multiple choice question for testing'; | ||||
|         learningObject.description = 'This multiple choice question was only created for testing purposes.'; | ||||
|         learningObject.contentType = DwengoContentType.GIFT; | ||||
|         learningObject.returnValue = { | ||||
|             callbackUrl: `/learningObject/${learningObject.hruid}/submissions`, | ||||
|             callbackSchema: '["antwoord vraag 1"]', | ||||
|         }; | ||||
|         learningObject.content = loadTestAsset('learning-objects/test-multiple-choice/content.txt'); | ||||
|         return learningObject; | ||||
|     }, | ||||
|     createAttachment: {}, | ||||
|     getHTMLRendering: () => loadTestAsset('learning-objects/test-multiple-choice/rendering.txt').toString(), | ||||
| }; | ||||
| 
 | ||||
| export default example; | ||||
|  | @ -1,3 +0,0 @@ | |||
| interface LearningPathExample { | ||||
|     createLearningPath: () => LearningPath; | ||||
| } | ||||
|  | @ -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; | ||||
| } | ||||
|  | @ -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; | ||||
|  | @ -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, | ||||
|     }; | ||||
| } | ||||
|  | @ -1,8 +1,8 @@ | |||
| import { AssertionError } from 'node:assert'; | ||||
| import { LearningObject } from '../../src/entities/content/learning-object.entity'; | ||||
| import { LearningPath as LearningPathEntity } from '../../src/entities/content/learning-path.entity'; | ||||
| import { expect } from 'vitest'; | ||||
| import { FilteredLearningObject, LearningPath } from '@dwengo-1/common/interfaces/learning-content'; | ||||
| import { RequiredEntityData } from '@mikro-orm/core'; | ||||
| 
 | ||||
| // Ignored properties because they belang for example to the class, not to the entity itself.
 | ||||
| const IGNORE_PROPERTIES = ['parent']; | ||||
|  | @ -11,53 +11,44 @@ const IGNORE_PROPERTIES = ['parent']; | |||
|  * 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 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 { | ||||
|     if (!actual.name) { | ||||
|         actual.name = 'actual'; | ||||
|     } | ||||
|     if (!expected.name) { | ||||
|         expected.name = 'expected'; | ||||
|     } | ||||
|     for (const property in expected.entity) { | ||||
|         if ( | ||||
|             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.
 | ||||
|             typeof expected.entity[property] !== 'function' // Functions obviously are not persisted via the database
 | ||||
|         ) { | ||||
|             if (!Object.prototype.hasOwnProperty.call(actual.entity, property)) { | ||||
|                 throw new AssertionError({ | ||||
|                     message: `${expected.name} has defined property ${property}, but ${actual.name} is missing it.`, | ||||
|                 }); | ||||
|             } | ||||
|             if (typeof expected.entity[property] === 'boolean') { | ||||
|                 // 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])) { | ||||
| export function expectToBeCorrectEntity<T extends object>(actual: T, expected: T, propertyPrefix = ''): void { | ||||
|     for (const property in expected) { | ||||
|         if (Object.prototype.hasOwnProperty.call(expected, property)) { | ||||
|             const prefixedProperty = propertyPrefix + property; | ||||
|             if ( | ||||
|                 property in IGNORE_PROPERTIES && | ||||
|                 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[property] !== 'function' // Functions obviously are not persisted via the database
 | ||||
|             ) { | ||||
|                 if (!Object.prototype.hasOwnProperty.call(actual, property)) { | ||||
|                     throw new AssertionError({ | ||||
|                         message: `${property} was ${expected.entity[property]} in ${expected.name},
 | ||||
|                         but ${actual.entity[property]} (${Boolean(expected.entity[property])}) in ${actual.name}`,
 | ||||
|                         message: `Expected property ${prefixedProperty}, but it is missing.`, | ||||
|                     }); | ||||
|                 } | ||||
|             } else if (typeof expected.entity[property] !== typeof actual.entity[property]) { | ||||
|                 throw new AssertionError({ | ||||
|                     message: `${property} has type ${typeof expected.entity[property]} in ${expected.name}, but type ${typeof actual.entity[property]} in ${actual.name}.`, | ||||
|                 }); | ||||
|             } else if (typeof expected.entity[property] === 'object') { | ||||
|                 expectToBeCorrectEntity( | ||||
|                     { | ||||
|                         name: actual.name + '.' + property, | ||||
|                         entity: actual.entity[property] as object, | ||||
|                     }, | ||||
|                     { | ||||
|                         name: expected.name + '.' + property, | ||||
|                         entity: expected.entity[property] as object, | ||||
|                 if (typeof expected[property] === 'boolean') { | ||||
|                     // Sometimes, booleans get represented by numbers 0 and 1 in the objects actual from the database.
 | ||||
|                     if (Boolean(expected[property]) !== Boolean(actual[property])) { | ||||
|                         throw new AssertionError({ | ||||
|                             message: `Expected ${prefixedProperty} to be ${expected[property]},
 | ||||
|                         but was ${actual[property]} (${Boolean(expected[property])}).`,
 | ||||
|                         }); | ||||
|                     } | ||||
|                 ); | ||||
|             } else { | ||||
|                 if (expected.entity[property] !== actual.entity[property]) { | ||||
|                 } else if (typeof expected[property] !== typeof actual[property]) { | ||||
|                     throw new AssertionError({ | ||||
|                         message: `${property} was ${expected.entity[property]} in ${expected.name}, but ${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[property] === 'object') { | ||||
|                     expectToBeCorrectEntity(actual[property] as object, expected[property] as object, property); | ||||
|                 } else { | ||||
|                     if (expected[property] !== actual[property]) { | ||||
|                         throw new AssertionError({ | ||||
|                             message: `${prefixedProperty} was expected to be ${expected[property]}, ` + `but was ${actual[property]}.`, | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | @ -67,9 +58,9 @@ export function expectToBeCorrectEntity<T extends object>(actual: { entity: T; n | |||
| /** | ||||
|  * Checks that filtered is the correct representation of original as FilteredLearningObject. | ||||
|  * @param filtered the representation as FilteredLearningObject | ||||
|  * @param original the original entity added to the database | ||||
|  * @param original the data of the entity in the database that was filtered. | ||||
|  */ | ||||
| export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearningObject, original: LearningObject): void { | ||||
| export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearningObject, original: RequiredEntityData<LearningObject>): void { | ||||
|     expect(filtered.uuid).toEqual(original.uuid); | ||||
|     expect(filtered.version).toEqual(original.version); | ||||
|     expect(filtered.language).toEqual(original.language); | ||||
|  | @ -97,54 +88,55 @@ export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearni | |||
|  * is a correct representation of the given learning path entity. | ||||
|  * | ||||
|  * @param learningPath The learning path returned by the retriever, service or endpoint | ||||
|  * @param expectedEntity The expected entity | ||||
|  * @param learningObjectsOnPath The learning objects on LearningPath. Necessary since some information in | ||||
|  *                              the learning path returned from the API endpoint | ||||
|  * @param expected The learning path that should have been returned. | ||||
|  */ | ||||
| export function expectToBeCorrectLearningPath( | ||||
|     learningPath: LearningPath, | ||||
|     expectedEntity: LearningPathEntity, | ||||
|     learningObjectsOnPath: FilteredLearningObject[] | ||||
| ): void { | ||||
|     expect(learningPath.hruid).toEqual(expectedEntity.hruid); | ||||
|     expect(learningPath.language).toEqual(expectedEntity.language); | ||||
|     expect(learningPath.description).toEqual(expectedEntity.description); | ||||
|     expect(learningPath.title).toEqual(expectedEntity.title); | ||||
| export function expectToBeCorrectLearningPath(learningPath: LearningPath, expected: LearningPath): void { | ||||
|     expect(learningPath.hruid).toEqual(expected.hruid); | ||||
|     expect(learningPath.language).toEqual(expected.language); | ||||
|     expect(learningPath.description).toEqual(expected.description); | ||||
|     expect(learningPath.title).toEqual(expected.title); | ||||
| 
 | ||||
|     const keywords = new Set(learningObjectsOnPath.flatMap((it) => it.keywords || [])); | ||||
|     expect(new Set(learningPath.keywords.split(' '))).toEqual(keywords); | ||||
|     expect(new Set(learningPath.keywords.split(' '))).toEqual(new Set(learningPath.keywords.split(' '))); | ||||
| 
 | ||||
|     const targetAges = new Set(learningObjectsOnPath.flatMap((it) => it.targetAges || [])); | ||||
|     expect(new Set(learningPath.target_ages)).toEqual(targetAges); | ||||
|     expect(learningPath.min_age).toEqual(Math.min(...targetAges)); | ||||
|     expect(learningPath.max_age).toEqual(Math.max(...targetAges)); | ||||
|     expect(new Set(learningPath.target_ages)).toEqual(new Set(expected.target_ages)); | ||||
|     expect(learningPath.min_age).toEqual(Math.min(...expected.target_ages)); | ||||
|     expect(learningPath.max_age).toEqual(Math.max(...expected.target_ages)); | ||||
| 
 | ||||
|     expect(learningPath.num_nodes).toEqual(expectedEntity.nodes.length); | ||||
|     expect(learningPath.image || null).toEqual(expectedEntity.image); | ||||
|     expect(learningPath.num_nodes).toEqual(expected.nodes.length); | ||||
|     expect(learningPath.image ?? null).toEqual(expected.image ?? null); | ||||
| 
 | ||||
|     const expectedLearningPathNodes = new Map( | ||||
|         expectedEntity.nodes.map((node) => [ | ||||
|             { learningObjectHruid: node.learningObjectHruid, language: node.language, version: node.version }, | ||||
|             { startNode: node.startNode, transitions: node.transitions }, | ||||
|         ]) | ||||
|     ); | ||||
| 
 | ||||
|     for (const node of learningPath.nodes) { | ||||
|         const nodeKey = { | ||||
|             learningObjectHruid: node.learningobject_hruid, | ||||
|             language: node.language, | ||||
|             version: node.version, | ||||
|         }; | ||||
|         expect(expectedLearningPathNodes.keys()).toContainEqual(nodeKey); | ||||
|         const expectedNode = [...expectedLearningPathNodes.entries()].find( | ||||
|             ([key, _]) => key.learningObjectHruid === nodeKey.learningObjectHruid && key.language === node.language && key.version === node.version | ||||
|         )![1]; | ||||
|         expect(node.start_node).toEqual(expectedNode.startNode); | ||||
| 
 | ||||
|         expect(new Set(node.transitions.map((it) => it.next.hruid))).toEqual( | ||||
|             new Set(expectedNode.transitions.map((it) => it.next.learningObjectHruid)) | ||||
|     for (const node of expected.nodes) { | ||||
|         const correspondingNode = learningPath.nodes.find( | ||||
|             (it) => node.learningobject_hruid === it.learningobject_hruid && node.language === it.language && node.version === it.version | ||||
|         ); | ||||
|         expect(new Set(node.transitions.map((it) => it.next.language))).toEqual(new Set(expectedNode.transitions.map((it) => it.next.language))); | ||||
|         expect(new Set(node.transitions.map((it) => it.next.version))).toEqual(new Set(expectedNode.transitions.map((it) => it.next.version))); | ||||
|         expect(correspondingNode).toBeTruthy(); | ||||
|         expect(Boolean(correspondingNode!.start_node)).toEqual(Boolean(node.start_node)); | ||||
| 
 | ||||
|         for (const transition of node.transitions) { | ||||
|             const correspondingTransition = correspondingNode!.transitions.find( | ||||
|                 (it) => | ||||
|                     it.next.hruid === transition.next.hruid && | ||||
|                     it.next.language === transition.next.language && | ||||
|                     it.next.version === transition.next.version | ||||
|             ); | ||||
|             expect(correspondingTransition).toBeTruthy(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Expect that the given result is a singleton list with exactly the given element. | ||||
|  */ | ||||
| export function expectToHaveFoundPrecisely<T extends object>(expected: T, result: T[]): void { | ||||
|     expect(result).toHaveProperty('length'); | ||||
|     expect(result.length).toBe(1); | ||||
|     expectToBeCorrectEntity(result[0], expected); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Expect that the given result is an empty list. | ||||
|  */ | ||||
| export function expectToHaveFoundNothing<T>(result: T[]): void { | ||||
|     expect(result).toHaveProperty('length'); | ||||
|     expect(result.length).toBe(0); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										10
									
								
								backend/tests/test-utils/get-html-rendering.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								backend/tests/test-utils/get-html-rendering.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| import { RequiredEntityData } from '@mikro-orm/core'; | ||||
| import { loadTestAsset } from './load-test-asset'; | ||||
| import { LearningObject } from '../../src/entities/content/learning-object.entity'; | ||||
| import { envVars, getEnvVar } from '../../src/util/envVars'; | ||||
| 
 | ||||
| export function getHtmlRenderingForTestLearningObject(learningObject: RequiredEntityData<LearningObject>): string { | ||||
|     const userPrefix = getEnvVar(envVars.UserContentPrefix); | ||||
|     const cleanedHruid = learningObject.hruid.startsWith(userPrefix) ? learningObject.hruid.substring(userPrefix.length) : learningObject.hruid; | ||||
|     return loadTestAsset(`/content/learning-object-resources/${cleanedHruid}/rendering.txt`).toString(); | ||||
| } | ||||
|  | @ -1,10 +1,14 @@ | |||
| import fs from 'fs'; | ||||
| import path from 'node:path'; | ||||
| import { fileURLToPath } from 'node:url'; | ||||
| 
 | ||||
| const fileName = fileURLToPath(import.meta.url); | ||||
| const dirName = path.dirname(fileName); | ||||
| 
 | ||||
| /** | ||||
|  * Load the asset at the given path. | ||||
|  * @param relPath Path of the asset relative to the test-assets folder. | ||||
|  */ | ||||
| export function loadTestAsset(relPath: string): Buffer { | ||||
|     return fs.readFileSync(path.resolve(__dirname, `../test-assets/${relPath}`)); | ||||
|     return fs.readFileSync(path.resolve(dirName, `../test_assets/${relPath}`)); | ||||
| } | ||||
|  |  | |||
|  | @ -2,11 +2,13 @@ import { EntityManager } from '@mikro-orm/core'; | |||
| import { Assignment } from '../../../src/entities/assignments/assignment.entity'; | ||||
| import { Class } from '../../../src/entities/classes/class.entity'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import { testLearningPathWithConditions } from '../content/learning-paths.testdata'; | ||||
| import { getClassWithTestleerlingAndTestleerkracht } from '../classes/classes.testdata'; | ||||
| 
 | ||||
| export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assignment[] { | ||||
|     const assignment01 = em.create(Assignment, { | ||||
|     assignment01 = em.create(Assignment, { | ||||
|         id: 21000, | ||||
|         within: classes[0], | ||||
|         id: 1, | ||||
|         title: 'dire straits', | ||||
|         description: 'reading', | ||||
|         learningPathHruid: 'id02', | ||||
|  | @ -14,9 +16,9 @@ export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assign | |||
|         groups: [], | ||||
|     }); | ||||
| 
 | ||||
|     const assignment02 = em.create(Assignment, { | ||||
|     assignment02 = em.create(Assignment, { | ||||
|         id: 21001, | ||||
|         within: classes[1], | ||||
|         id: 2, | ||||
|         title: 'tool', | ||||
|         description: 'reading', | ||||
|         learningPathHruid: 'id01', | ||||
|  | @ -24,9 +26,9 @@ export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assign | |||
|         groups: [], | ||||
|     }); | ||||
| 
 | ||||
|     const assignment03 = em.create(Assignment, { | ||||
|     assignment03 = em.create(Assignment, { | ||||
|         id: 21002, | ||||
|         within: classes[0], | ||||
|         id: 3, | ||||
|         title: 'delete', | ||||
|         description: 'will be deleted', | ||||
|         learningPathHruid: 'id02', | ||||
|  | @ -34,9 +36,9 @@ export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assign | |||
|         groups: [], | ||||
|     }); | ||||
| 
 | ||||
|     const assignment04 = em.create(Assignment, { | ||||
|     assignment04 = em.create(Assignment, { | ||||
|         id: 21003, | ||||
|         within: classes[0], | ||||
|         id: 4, | ||||
|         title: 'another assignment', | ||||
|         description: 'with a description', | ||||
|         learningPathHruid: 'id01', | ||||
|  | @ -44,5 +46,41 @@ export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assign | |||
|         groups: [], | ||||
|     }); | ||||
| 
 | ||||
|     return [assignment01, assignment02, assignment03, assignment04]; | ||||
|     conditionalPathAssignment = em.create(Assignment, { | ||||
|         within: getClassWithTestleerlingAndTestleerkracht(), | ||||
|         id: 21004, | ||||
|         title: 'Assignment: Conditional Learning Path', | ||||
|         description: 'You have to do the testing learning path with a condition.', | ||||
|         learningPathHruid: testLearningPathWithConditions.hruid, | ||||
|         learningPathLanguage: testLearningPathWithConditions.language as Language, | ||||
|         groups: [], | ||||
|     }); | ||||
| 
 | ||||
|     return [assignment01, assignment02, assignment03, assignment04, conditionalPathAssignment]; | ||||
| } | ||||
| 
 | ||||
| let assignment01: Assignment; | ||||
| let assignment02: Assignment; | ||||
| let assignment03: Assignment; | ||||
| let assignment04: Assignment; | ||||
| let conditionalPathAssignment: Assignment; | ||||
| 
 | ||||
| export function getAssignment01(): Assignment { | ||||
|     return assignment01; | ||||
| } | ||||
| 
 | ||||
| export function getAssignment02(): Assignment { | ||||
|     return assignment02; | ||||
| } | ||||
| 
 | ||||
| export function getAssignment03(): Assignment { | ||||
|     return assignment03; | ||||
| } | ||||
| 
 | ||||
| export function getAssignment04(): Assignment { | ||||
|     return assignment04; | ||||
| } | ||||
| 
 | ||||
| export function getConditionalPathAssignment(): Assignment { | ||||
|     return conditionalPathAssignment; | ||||
| } | ||||
|  |  | |||
|  | @ -2,15 +2,17 @@ import { EntityManager } from '@mikro-orm/core'; | |||
| import { Group } from '../../../src/entities/assignments/group.entity'; | ||||
| import { Assignment } from '../../../src/entities/assignments/assignment.entity'; | ||||
| import { Student } from '../../../src/entities/users/student.entity'; | ||||
| import { getConditionalPathAssignment } from './assignments.testdata'; | ||||
| import { getTestleerling1 } from '../users/students.testdata'; | ||||
| 
 | ||||
| export function makeTestGroups(em: EntityManager, students: Student[], assignments: Assignment[]): Group[] { | ||||
|     /* | ||||
|      * Group #1 for Assignment #1 in class 'id01' | ||||
|      * => Assigned to do learning path 'id02' | ||||
|      */ | ||||
|     const group01 = em.create(Group, { | ||||
|     group01 = em.create(Group, { | ||||
|         assignment: assignments[0], | ||||
|         groupNumber: 1, | ||||
|         groupNumber: 21001, | ||||
|         members: students.slice(0, 2), | ||||
|     }); | ||||
| 
 | ||||
|  | @ -18,9 +20,9 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen | |||
|      * Group #2 for Assignment #1 in class 'id01' | ||||
|      * => Assigned to do learning path 'id02' | ||||
|      */ | ||||
|     const group02 = em.create(Group, { | ||||
|     group02 = em.create(Group, { | ||||
|         assignment: assignments[0], | ||||
|         groupNumber: 2, | ||||
|         groupNumber: 21002, | ||||
|         members: students.slice(2, 4), | ||||
|     }); | ||||
| 
 | ||||
|  | @ -28,9 +30,9 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen | |||
|      * Group #3 for Assignment #1 in class 'id01' | ||||
|      * => Assigned to do learning path 'id02' | ||||
|      */ | ||||
|     const group03 = em.create(Group, { | ||||
|     group03 = em.create(Group, { | ||||
|         assignment: assignments[0], | ||||
|         groupNumber: 3, | ||||
|         groupNumber: 21003, | ||||
|         members: students.slice(4, 6), | ||||
|     }); | ||||
| 
 | ||||
|  | @ -38,9 +40,9 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen | |||
|      * Group #4 for Assignment #2 in class 'id02' | ||||
|      * => Assigned to do learning path 'id01' | ||||
|      */ | ||||
|     const group04 = em.create(Group, { | ||||
|     group04 = em.create(Group, { | ||||
|         assignment: assignments[1], | ||||
|         groupNumber: 4, | ||||
|         groupNumber: 21004, | ||||
|         members: students.slice(3, 4), | ||||
|     }); | ||||
| 
 | ||||
|  | @ -48,11 +50,51 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen | |||
|      * Group #5 for Assignment #4 in class 'id01' | ||||
|      * => Assigned to do learning path 'id01' | ||||
|      */ | ||||
|     const group05 = em.create(Group, { | ||||
|     group05 = em.create(Group, { | ||||
|         assignment: assignments[3], | ||||
|         groupNumber: 1, | ||||
|         groupNumber: 21001, | ||||
|         members: students.slice(0, 2), | ||||
|     }); | ||||
| 
 | ||||
|     return [group01, group02, group03, group04, group05]; | ||||
|     /** | ||||
|      * Group 1 for the assignment of the testing learning path with conditions. | ||||
|      */ | ||||
|     group1ConditionalLearningPath = em.create(Group, { | ||||
|         assignment: getConditionalPathAssignment(), | ||||
|         groupNumber: 21005, | ||||
|         members: [getTestleerling1()], | ||||
|     }); | ||||
| 
 | ||||
|     return [group01, group02, group03, group04, group05, group1ConditionalLearningPath]; | ||||
| } | ||||
| 
 | ||||
| let group01: Group; | ||||
| let group02: Group; | ||||
| let group03: Group; | ||||
| let group04: Group; | ||||
| let group05: Group; | ||||
| let group1ConditionalLearningPath: Group; | ||||
| 
 | ||||
| export function getTestGroup01(): Group { | ||||
|     return group01; | ||||
| } | ||||
| 
 | ||||
| export function getTestGroup02(): Group { | ||||
|     return group02; | ||||
| } | ||||
| 
 | ||||
| export function getTestGroup03(): Group { | ||||
|     return group03; | ||||
| } | ||||
| 
 | ||||
| export function getTestGroup04(): Group { | ||||
|     return group04; | ||||
| } | ||||
| 
 | ||||
| export function getTestGroup05(): Group { | ||||
|     return group05; | ||||
| } | ||||
| 
 | ||||
| export function getGroup1ConditionalLearningPath(): Group { | ||||
|     return group1ConditionalLearningPath; | ||||
| } | ||||
|  |  | |||
|  | @ -2,12 +2,14 @@ import { EntityManager } from '@mikro-orm/core'; | |||
| import { Class } from '../../../src/entities/classes/class.entity'; | ||||
| import { Student } from '../../../src/entities/users/student.entity'; | ||||
| import { Teacher } from '../../../src/entities/users/teacher.entity'; | ||||
| import { getTestleerkracht1 } from '../users/teachers.testdata'; | ||||
| import { getTestleerling1 } from '../users/students.testdata'; | ||||
| 
 | ||||
| export function makeTestClasses(em: EntityManager, students: Student[], teachers: Teacher[]): Class[] { | ||||
|     const studentsClass01 = students.slice(0, 8); | ||||
|     const teacherClass01: Teacher[] = teachers.slice(4, 5); | ||||
| 
 | ||||
|     const class01 = em.create(Class, { | ||||
|     class01 = em.create(Class, { | ||||
|         classId: '8764b861-90a6-42e5-9732-c0d9eb2f55f9', | ||||
|         displayName: 'class01', | ||||
|         teachers: teacherClass01, | ||||
|  | @ -17,7 +19,7 @@ export function makeTestClasses(em: EntityManager, students: Student[], teachers | |||
|     const studentsClass02: Student[] = students.slice(0, 2).concat(students.slice(3, 4)); | ||||
|     const teacherClass02: Teacher[] = teachers.slice(1, 2); | ||||
| 
 | ||||
|     const class02 = em.create(Class, { | ||||
|     class02 = em.create(Class, { | ||||
|         classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||
|         displayName: 'class02', | ||||
|         teachers: teacherClass02, | ||||
|  | @ -27,7 +29,7 @@ export function makeTestClasses(em: EntityManager, students: Student[], teachers | |||
|     const studentsClass03: Student[] = students.slice(1, 4); | ||||
|     const teacherClass03: Teacher[] = teachers.slice(2, 3); | ||||
| 
 | ||||
|     const class03 = em.create(Class, { | ||||
|     class03 = em.create(Class, { | ||||
|         classId: '80dcc3e0-1811-4091-9361-42c0eee91cfa', | ||||
|         displayName: 'class03', | ||||
|         teachers: teacherClass03, | ||||
|  | @ -37,12 +39,45 @@ export function makeTestClasses(em: EntityManager, students: Student[], teachers | |||
|     const studentsClass04: Student[] = students.slice(0, 2); | ||||
|     const teacherClass04: Teacher[] = teachers.slice(2, 3); | ||||
| 
 | ||||
|     const class04 = em.create(Class, { | ||||
|     class04 = em.create(Class, { | ||||
|         classId: '33d03536-83b8-4880-9982-9bbf2f908ddf', | ||||
|         displayName: 'class04', | ||||
|         teachers: teacherClass04, | ||||
|         students: studentsClass04, | ||||
|     }); | ||||
| 
 | ||||
|     return [class01, class02, class03, class04]; | ||||
|     classWithTestleerlingAndTestleerkracht = em.create(Class, { | ||||
|         classId: 'a75298b5-18aa-471d-8eeb-5d77eb989393', | ||||
|         displayName: 'Testklasse', | ||||
|         teachers: [getTestleerkracht1()], | ||||
|         students: [getTestleerling1()], | ||||
|     }); | ||||
| 
 | ||||
|     return [class01, class02, class03, class04, classWithTestleerlingAndTestleerkracht]; | ||||
| } | ||||
| 
 | ||||
| let class01: Class; | ||||
| let class02: Class; | ||||
| let class03: Class; | ||||
| let class04: Class; | ||||
| let classWithTestleerlingAndTestleerkracht: Class; | ||||
| 
 | ||||
| export function getClass01(): Class { | ||||
|     return class01; | ||||
| } | ||||
| 
 | ||||
| export function getClass02(): Class { | ||||
|     return class02; | ||||
| } | ||||
| 
 | ||||
| export function getClass03(): Class { | ||||
|     return class03; | ||||
| } | ||||
| 
 | ||||
| export function getClass04(): Class { | ||||
|     return class04; | ||||
| } | ||||
| 
 | ||||
| export function getClassWithTestleerlingAndTestleerkracht(): Class { | ||||
|     return classWithTestleerlingAndTestleerkracht; | ||||
| } | ||||
|  |  | |||
| Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB | 
| Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB | 
|  | @ -0,0 +1,2 @@ | |||
| ::Reflection:: | ||||
| Reflect on this learning path. What have you learned today? {} | ||||
|  | @ -0,0 +1,7 @@ | |||
| <div class="learning-object-gift"> | ||||
|     <div id="gift-q1" class="gift-question gift-question-type-Essay"> | ||||
|         <h2 id="gift-q1-title" class="gift-title">Reflection</h2> | ||||
|         <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> | ||||
|     </div> | ||||
| </div> | ||||
|  | @ -1,5 +1,5 @@ | |||
| ::MC basic:: | ||||
| Are you following along well with the class?  { | ||||
| ::Self-evaluation:: | ||||
| Are you following along well?  { | ||||
| 	~No, it's very difficult to follow along. | ||||
|     =Yes, no problem! | ||||
| } | ||||
|  | @ -1,14 +1,14 @@ | |||
| <div class="learning-object-gift"> | ||||
|     <div id="gift-q1" class="gift-question"> | ||||
|         <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> | ||||
|     <div id="gift-q1" class="gift-question gift-question-type-MC"> | ||||
|         <h2 id="gift-q1-title" class="gift-title">Self-evaluation</h2> | ||||
|         <p id="gift-q1-stem" class="gift-stem">Are you following along well?</p> | ||||
|         <div class="gift-choice-div"> | ||||
|             <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">No, it's very difficult to follow along.</label> | ||||
|         </div> | ||||
|         <div class="gift-choice-div"> | ||||
|             <input value="1" name="gift-q1-choices" id="gift-q1-choice-1" type="radio"> | ||||
|             <label for="gift-q1-choice-1">[object Object]</label> | ||||
|             <label for="gift-q1-choice-1">Yes, no problem!</label> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | @ -1,135 +1,261 @@ | |||
| import { EntityManager } from '@mikro-orm/core'; | ||||
| import { EntityManager, RequiredEntityData } from '@mikro-orm/core'; | ||||
| import { LearningObject } from '../../../src/entities/content/learning-object.entity'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import { DwengoContentType } from '../../../src/services/learning-objects/processing/content-type'; | ||||
| import { ReturnValue } from '../../../src/entities/content/return-value.entity'; | ||||
| import { envVars, getEnvVar } from '../../../src/util/envVars'; | ||||
| import { loadTestAsset } from '../../test-utils/load-test-asset'; | ||||
| import { v4 } from 'uuid'; | ||||
| 
 | ||||
| export function makeTestLearningObjects(em: EntityManager): LearningObject[] { | ||||
|     const returnValue: ReturnValue = new ReturnValue(); | ||||
|     returnValue.callbackSchema = ''; | ||||
|     returnValue.callbackUrl = ''; | ||||
| 
 | ||||
|     const learningObject01 = em.create(LearningObject, { | ||||
|         hruid: 'id01', | ||||
|         language: Language.English, | ||||
|         version: 1, | ||||
|         admins: [], | ||||
|         title: 'Undertow', | ||||
|         description: 'debute', | ||||
|         contentType: DwengoContentType.TEXT_MARKDOWN, | ||||
|         keywords: [], | ||||
|         teacherExclusive: false, | ||||
|         skosConcepts: [], | ||||
|         educationalGoals: [], | ||||
|         copyright: '', | ||||
|         license: '', | ||||
|         estimatedTime: 45, | ||||
|         returnValue: returnValue, | ||||
|         available: true, | ||||
|         contentLocation: '', | ||||
|         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"), | ||||
|     }); | ||||
|     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 learningObject02 = em.create(LearningObject, { | ||||
|         hruid: 'id02', | ||||
|         language: Language.English, | ||||
|         version: 1, | ||||
|         admins: [], | ||||
|         title: 'Aenema', | ||||
|         description: 'second album', | ||||
|         contentType: DwengoContentType.TEXT_MARKDOWN, | ||||
|         keywords: [], | ||||
|         teacherExclusive: false, | ||||
|         skosConcepts: [], | ||||
|         educationalGoals: [], | ||||
|         copyright: '', | ||||
|         license: '', | ||||
|         estimatedTime: 80, | ||||
|         returnValue: returnValue, | ||||
|         available: true, | ||||
|         contentLocation: '', | ||||
|         attachments: [], | ||||
|         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" | ||||
|         ), | ||||
|     }); | ||||
|     const learningObjectMultipleChoice = em.create(LearningObject, testLearningObjectMultipleChoice); | ||||
|     const learningObjectEssayQuestion = em.create(LearningObject, testLearningObjectEssayQuestion); | ||||
| 
 | ||||
|     const learningObject03 = em.create(LearningObject, { | ||||
|         hruid: 'id03', | ||||
|         language: Language.English, | ||||
|         version: 1, | ||||
|         admins: [], | ||||
|         title: 'love over gold', | ||||
|         description: 'third album', | ||||
|         contentType: DwengoContentType.TEXT_MARKDOWN, | ||||
|         keywords: [], | ||||
|         teacherExclusive: false, | ||||
|         skosConcepts: [], | ||||
|         educationalGoals: [], | ||||
|         copyright: '', | ||||
|         license: '', | ||||
|         estimatedTime: 55, | ||||
|         returnValue: returnValue, | ||||
|         available: true, | ||||
|         contentLocation: '', | ||||
|         attachments: [], | ||||
|         content: Buffer.from( | ||||
|             'he wrote me a prescription, he said you are depressed, \ | ||||
|                     but I am glad you came to see me to get this off your chest, \ | ||||
|                     come back and see me later next patient please \ | ||||
|                     send in another victim of industrial disease' | ||||
|         ), | ||||
|     }); | ||||
|     const learningObjectPnNotebooks = em.create(LearningObject, testLearningObjectPnNotebooks); | ||||
| 
 | ||||
|     const learningObject04 = em.create(LearningObject, { | ||||
|         hruid: 'id04', | ||||
|         language: Language.English, | ||||
|         version: 1, | ||||
|         admins: [], | ||||
|         title: 'making movies', | ||||
|         description: 'fifth album', | ||||
|         contentType: DwengoContentType.TEXT_MARKDOWN, | ||||
|         keywords: [], | ||||
|         teacherExclusive: false, | ||||
|         skosConcepts: [], | ||||
|         educationalGoals: [], | ||||
|         copyright: '', | ||||
|         license: '', | ||||
|         estimatedTime: 55, | ||||
|         returnValue: returnValue, | ||||
|         available: true, | ||||
|         contentLocation: '', | ||||
|         attachments: [], | ||||
|         content: Buffer.from( | ||||
|             'I put my hand upon the lever \ | ||||
|                     Said let it rock and let it roll \ | ||||
|                     I had the one-arm bandit fever \ | ||||
|                     There was an arrow through my heart and my soul' | ||||
|         ), | ||||
|     }); | ||||
| 
 | ||||
|     const learningObject05 = em.create(LearningObject, { | ||||
|         hruid: 'id05', | ||||
|         language: Language.English, | ||||
|         version: 1, | ||||
|         admins: [], | ||||
|         title: 'on every street', | ||||
|         description: 'sixth album', | ||||
|         contentType: DwengoContentType.TEXT_MARKDOWN, | ||||
|         keywords: [], | ||||
|         teacherExclusive: false, | ||||
|         skosConcepts: [], | ||||
|         educationalGoals: [], | ||||
|         copyright: '', | ||||
|         license: '', | ||||
|         estimatedTime: 55, | ||||
|         returnValue: returnValue, | ||||
|         available: true, | ||||
|         contentLocation: '', | ||||
|         attachments: [], | ||||
|         content: Buffer.from('calling Elvis, is anybody home, calling elvis, I am here all alone'), | ||||
|     }); | ||||
| 
 | ||||
|     return [learningObject01, learningObject02, learningObject03, learningObject04, learningObject05]; | ||||
|     return [ | ||||
|         learningObject01, | ||||
|         learningObject02, | ||||
|         learningObject03, | ||||
|         learningObject04, | ||||
|         learningObject05, | ||||
|         learningObjectMultipleChoice, | ||||
|         learningObjectEssayQuestion, | ||||
|         learningObjectPnNotebooks, | ||||
|     ]; | ||||
| } | ||||
| 
 | ||||
| export function createReturnValue(): ReturnValue { | ||||
|     const returnValue: ReturnValue = new ReturnValue(); | ||||
|     returnValue.callbackSchema = '[]'; | ||||
|     returnValue.callbackUrl = '%SUBMISSION%'; | ||||
|     return returnValue; | ||||
| } | ||||
| 
 | ||||
| export const testLearningObject01: RequiredEntityData<LearningObject> = { | ||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}id01`, | ||||
|     language: Language.English, | ||||
|     version: 1, | ||||
|     admins: [], | ||||
|     title: 'Undertow', | ||||
|     description: 'debute', | ||||
|     contentType: DwengoContentType.TEXT_MARKDOWN, | ||||
|     keywords: [], | ||||
|     uuid: v4(), | ||||
|     targetAges: [16, 17, 18], | ||||
|     teacherExclusive: false, | ||||
|     skosConcepts: [], | ||||
|     educationalGoals: [], | ||||
|     copyright: '', | ||||
|     license: '', | ||||
|     estimatedTime: 45, | ||||
|     returnValue: createReturnValue(), | ||||
|     available: true, | ||||
|     contentLocation: '', | ||||
|     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"), | ||||
| }; | ||||
| 
 | ||||
| export const testLearningObject02: RequiredEntityData<LearningObject> = { | ||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}id02`, | ||||
|     language: Language.English, | ||||
|     version: 1, | ||||
|     admins: [], | ||||
|     title: 'Aenema', | ||||
|     description: 'second album', | ||||
|     contentType: DwengoContentType.TEXT_MARKDOWN, | ||||
|     keywords: [], | ||||
|     teacherExclusive: false, | ||||
|     skosConcepts: [], | ||||
|     educationalGoals: [], | ||||
|     copyright: '', | ||||
|     license: '', | ||||
|     estimatedTime: 80, | ||||
|     returnValue: createReturnValue(), | ||||
|     available: true, | ||||
|     contentLocation: '', | ||||
|     attachments: [], | ||||
|     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" | ||||
|     ), | ||||
| }; | ||||
| 
 | ||||
| export const testLearningObject03: RequiredEntityData<LearningObject> = { | ||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}id03`, | ||||
|     language: Language.English, | ||||
|     version: 1, | ||||
|     admins: [], | ||||
|     title: 'love over gold', | ||||
|     description: 'third album', | ||||
|     contentType: DwengoContentType.TEXT_MARKDOWN, | ||||
|     keywords: [], | ||||
|     teacherExclusive: false, | ||||
|     skosConcepts: [], | ||||
|     educationalGoals: [], | ||||
|     copyright: '', | ||||
|     license: '', | ||||
|     estimatedTime: 55, | ||||
|     returnValue: createReturnValue(), | ||||
|     available: true, | ||||
|     contentLocation: '', | ||||
|     attachments: [], | ||||
|     content: Buffer.from( | ||||
|         'he wrote me a prescription, he said you are depressed, \ | ||||
|          but I am glad you came to see me to get this off your chest, \ | ||||
|          come back and see me later next patient please \ | ||||
|          send in another victim of industrial disease' | ||||
|     ), | ||||
| }; | ||||
| 
 | ||||
| export const testLearningObject04: RequiredEntityData<LearningObject> = { | ||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}id04`, | ||||
|     language: Language.English, | ||||
|     version: 1, | ||||
|     admins: [], | ||||
|     title: 'making movies', | ||||
|     description: 'fifth album', | ||||
|     contentType: DwengoContentType.TEXT_MARKDOWN, | ||||
|     keywords: [], | ||||
|     teacherExclusive: false, | ||||
|     skosConcepts: [], | ||||
|     educationalGoals: [], | ||||
|     copyright: '', | ||||
|     license: '', | ||||
|     estimatedTime: 55, | ||||
|     returnValue: createReturnValue(), | ||||
|     available: true, | ||||
|     contentLocation: '', | ||||
|     attachments: [], | ||||
|     content: Buffer.from( | ||||
|         'I put my hand upon the lever \ | ||||
|          Said let it rock and let it roll \ | ||||
|          I had the one-arm bandit fever \ | ||||
|          There was an arrow through my heart and my soul' | ||||
|     ), | ||||
| }; | ||||
| 
 | ||||
| export const testLearningObject05: RequiredEntityData<LearningObject> = { | ||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}id05`, | ||||
|     language: Language.English, | ||||
|     version: 1, | ||||
|     admins: [], | ||||
|     title: 'on every street', | ||||
|     description: 'sixth album', | ||||
|     contentType: DwengoContentType.TEXT_MARKDOWN, | ||||
|     keywords: [], | ||||
|     teacherExclusive: false, | ||||
|     skosConcepts: [], | ||||
|     educationalGoals: [], | ||||
|     copyright: '', | ||||
|     license: '', | ||||
|     estimatedTime: 55, | ||||
|     returnValue: createReturnValue(), | ||||
|     available: true, | ||||
|     contentLocation: '', | ||||
|     attachments: [], | ||||
|     content: Buffer.from('calling Elvis, is anybody home, calling elvis, I am here all alone'), | ||||
| }; | ||||
| 
 | ||||
| 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_question/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: '[]', | ||||
|     }, | ||||
| }; | ||||
|  |  | |||
|  | @ -1,100 +1,236 @@ | |||
| import { EntityManager } from '@mikro-orm/core'; | ||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||
| 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 { mapToLearningPath } from '../../../src/services/learning-paths/learning-path-service'; | ||||
| 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[] { | ||||
|     const learningPathNode01: LearningPathNode = new LearningPathNode(); | ||||
|     const learningPathNode02: LearningPathNode = new LearningPathNode(); | ||||
|     const learningPathNode03: LearningPathNode = new LearningPathNode(); | ||||
|     const learningPathNode04: LearningPathNode = new LearningPathNode(); | ||||
|     const learningPathNode05: LearningPathNode = new LearningPathNode(); | ||||
| export function makeTestLearningPaths(_em: EntityManager): LearningPath[] { | ||||
|     const learningPath01 = mapToLearningPath(testLearningPath01, []); | ||||
|     const learningPath02 = mapToLearningPath(testLearningPath02, []); | ||||
| 
 | ||||
|     const transitions01: LearningPathTransition = new LearningPathTransition(); | ||||
|     const transitions02: LearningPathTransition = new LearningPathTransition(); | ||||
|     const transitions03: LearningPathTransition = new LearningPathTransition(); | ||||
|     const transitions04: LearningPathTransition = new LearningPathTransition(); | ||||
|     const transitions05: LearningPathTransition = new LearningPathTransition(); | ||||
|     const partiallyDatabasePartiallyDwengoApiLearningPath = mapToLearningPath(testPartiallyDatabaseAndPartiallyDwengoApiLearningPath, []); | ||||
|     const learningPathWithConditions = mapToLearningPath(testLearningPathWithConditions, []); | ||||
| 
 | ||||
|     transitions01.condition = 'true'; | ||||
|     transitions01.next = learningPathNode02; | ||||
| 
 | ||||
|     transitions02.condition = 'true'; | ||||
|     transitions02.next = learningPathNode02; | ||||
| 
 | ||||
|     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, | ||||
|         admins: [], | ||||
|         title: 'repertoire Tool', | ||||
|         description: 'all about Tool', | ||||
|         image: null, | ||||
|         nodes: nodes01, | ||||
|     }); | ||||
| 
 | ||||
|     const nodes02: LearningPathNode[] = [ | ||||
|         // LearningPathNode03,
 | ||||
|         // LearningPathNode04,
 | ||||
|         // LearningPathNode05,
 | ||||
|     ]; | ||||
|     const learningPath02 = em.create(LearningPath, { | ||||
|         hruid: 'id02', | ||||
|         language: Language.English, | ||||
|         admins: [], | ||||
|         title: 'repertoire Dire Straits', | ||||
|         description: 'all about Dire Straits', | ||||
|         image: null, | ||||
|         nodes: nodes02, | ||||
|     }); | ||||
| 
 | ||||
|     return [learningPath01, learningPath02]; | ||||
|     return [learningPath01, learningPath02, partiallyDatabasePartiallyDwengoApiLearningPath, learningPathWithConditions]; | ||||
| } | ||||
| 
 | ||||
| const nowString = new Date().toString(); | ||||
| 
 | ||||
| export const testLearningPath01: LearningPathDTO = { | ||||
|     keywords: 'test', | ||||
|     target_ages: [16, 17, 18], | ||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}id01`, | ||||
|     language: Language.English, | ||||
|     title: 'repertoire Tool', | ||||
|     description: 'all about Tool', | ||||
|     nodes: [ | ||||
|         { | ||||
|             learningobject_hruid: testLearningObject01.hruid, | ||||
|             language: testLearningObject01.language, | ||||
|             version: testLearningObject01.version, | ||||
|             start_node: true, | ||||
|             created_at: nowString, | ||||
|             updatedAt: nowString, | ||||
|             transitions: [ | ||||
|                 { | ||||
|                     next: { | ||||
|                         hruid: testLearningObject02.hruid, | ||||
|                         language: testLearningObject02.language, | ||||
|                         version: testLearningObject02.version, | ||||
|                     }, | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|         { | ||||
|             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: `${getEnvVar(envVars.UserContentPrefix)}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: [10, 11, 12, 13, 14, 15, 16, 17, 18], | ||||
|     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: [], | ||||
|         }, | ||||
|     ], | ||||
| }; | ||||
|  |  | |||
|  | @ -15,7 +15,14 @@ export const TEST_STUDENTS = [ | |||
|     { username: 'testleerling1', firstName: 'Gerald', lastName: 'Schmittinger' }, | ||||
| ]; | ||||
| 
 | ||||
| let testStudents: Student[]; | ||||
| 
 | ||||
| // 🏗️ Functie die ORM entities maakt uit de data array
 | ||||
| export function makeTestStudents(em: EntityManager): Student[] { | ||||
|     return TEST_STUDENTS.map((data) => em.create(Student, data)); | ||||
|     testStudents = TEST_STUDENTS.map((data) => em.create(Student, data)); | ||||
|     return testStudents; | ||||
| } | ||||
| 
 | ||||
| export function getTestleerling1(): Student { | ||||
|     return testStudents.find((it) => it.username === 'testleerling1'); | ||||
| } | ||||
|  |  | |||
|  | @ -2,37 +2,63 @@ import { Teacher } from '../../../src/entities/users/teacher.entity'; | |||
| import { EntityManager } from '@mikro-orm/core'; | ||||
| 
 | ||||
| export function makeTestTeachers(em: EntityManager): Teacher[] { | ||||
|     const teacher01 = em.create(Teacher, { | ||||
|     teacher01 = em.create(Teacher, { | ||||
|         username: 'FooFighters', | ||||
|         firstName: 'Dave', | ||||
|         lastName: 'Grohl', | ||||
|     }); | ||||
| 
 | ||||
|     const teacher02 = em.create(Teacher, { | ||||
|     teacher02 = em.create(Teacher, { | ||||
|         username: 'LimpBizkit', | ||||
|         firstName: 'Fred', | ||||
|         lastName: 'Durst', | ||||
|     }); | ||||
| 
 | ||||
|     const teacher03 = em.create(Teacher, { | ||||
|     teacher03 = em.create(Teacher, { | ||||
|         username: 'Staind', | ||||
|         firstName: 'Aaron', | ||||
|         lastName: 'Lewis', | ||||
|     }); | ||||
| 
 | ||||
|     // Should not be used, gets deleted in a unit test
 | ||||
|     const teacher04 = em.create(Teacher, { | ||||
|     teacher04 = em.create(Teacher, { | ||||
|         username: 'ZesdeMetaal', | ||||
|         firstName: 'Wannes', | ||||
|         lastName: 'Cappelle', | ||||
|     }); | ||||
| 
 | ||||
|     // Makes sure when logged in as testleerkracht1, there exists a corresponding user
 | ||||
|     const teacher05 = em.create(Teacher, { | ||||
|     testleerkracht1 = em.create(Teacher, { | ||||
|         username: 'testleerkracht1', | ||||
|         firstName: 'Bob', | ||||
|         lastName: 'Dylan', | ||||
|         firstName: 'Kris', | ||||
|         lastName: 'Coolsaet', | ||||
|     }); | ||||
| 
 | ||||
|     return [teacher01, teacher02, teacher03, teacher04, teacher05]; | ||||
|     return [teacher01, teacher02, teacher03, teacher04, testleerkracht1]; | ||||
| } | ||||
| 
 | ||||
| let teacher01: Teacher; | ||||
| let teacher02: Teacher; | ||||
| let teacher03: Teacher; | ||||
| let teacher04: Teacher; | ||||
| let testleerkracht1: Teacher; | ||||
| 
 | ||||
| export function getTeacher01(): Teacher { | ||||
|     return teacher01; | ||||
| } | ||||
| 
 | ||||
| export function getTeacher02(): Teacher { | ||||
|     return teacher02; | ||||
| } | ||||
| 
 | ||||
| export function getTeacher03(): Teacher { | ||||
|     return teacher03; | ||||
| } | ||||
| 
 | ||||
| export function getTeacher04(): Teacher { | ||||
|     return teacher04; | ||||
| } | ||||
| 
 | ||||
| export function getTestleerkracht1(): Teacher { | ||||
|     return testleerkracht1; | ||||
| } | ||||
|  |  | |||
		Reference in a new issue
	
	 Adriaan Jacquet
						Adriaan Jacquet