@tinyweb_dev/oe-exam-sdk 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/components/exams/take/ExamTakingPageContainer.js +6 -0
  2. package/dist/components/exams/take/components/QuestionRenderer.js +7 -3
  3. package/dist/components/exams/take/components/question-renderers/MoversWordOrderingGroupRenderer.d.ts +13 -0
  4. package/dist/components/exams/take/components/question-renderers/MoversWordOrderingGroupRenderer.js +30 -0
  5. package/dist/components/exams/take/components/question-renderers/index.d.ts +1 -0
  6. package/dist/components/exams/take/components/question-renderers/index.js +1 -0
  7. package/dist/components/exams/take/utils/answer-transformers.js +8 -1
  8. package/dist/components/exams/take/utils/question-transformers.d.ts +16 -1
  9. package/dist/components/exams/take/utils/question-transformers.js +47 -2
  10. package/dist/components/questions/_shared/config/question-types.config.js +18 -0
  11. package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +1 -0
  12. package/dist/components/questions/_shared/types/index.d.ts +1 -0
  13. package/dist/components/questions/_shared/types/index.js +1 -0
  14. package/dist/components/questions/_shared/types/match-columns-to-make-sentences.type.d.ts +56 -0
  15. package/dist/components/questions/_shared/types/match-columns-to-make-sentences.type.js +2 -0
  16. package/dist/components/questions/_shared/types/match-word-to-picture-group.type.d.ts +35 -0
  17. package/dist/components/questions/_shared/types/match-word-to-picture-group.type.js +2 -0
  18. package/dist/components/questions/_shared/types/word-ordering-group.type.d.ts +34 -0
  19. package/dist/components/questions/_shared/types/word-ordering-group.type.js +2 -0
  20. package/dist/components/questions/components/QuestionSearchDropdown.js +3 -0
  21. package/dist/components/questions/components/QuestionTypeSelector.js +3 -0
  22. package/dist/components/questions/creator/question-type-registry.js +6 -0
  23. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +1 -1
  24. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +5 -3
  25. package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +16 -2
  26. package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +4 -0
  27. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.d.ts +2 -0
  28. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +74 -0
  29. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.d.ts +2 -0
  30. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.js +222 -0
  31. package/dist/components/questions/types/match-columns-to-make-sentences/index.d.ts +5 -0
  32. package/dist/components/questions/types/match-columns-to-make-sentences/index.js +5 -0
  33. package/dist/components/questions/types/match-columns-to-make-sentences/map-match-columns-to-make-sentences-data.d.ts +8 -0
  34. package/dist/components/questions/types/match-columns-to-make-sentences/map-match-columns-to-make-sentences-data.js +199 -0
  35. package/dist/components/questions/types/match-columns-to-make-sentences/register.d.ts +2 -0
  36. package/dist/components/questions/types/match-columns-to-make-sentences/register.js +36 -0
  37. package/dist/components/questions/types/match-columns-to-make-sentences/transform.d.ts +2 -0
  38. package/dist/components/questions/types/match-columns-to-make-sentences/transform.js +66 -0
  39. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.d.ts +2 -0
  40. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +65 -0
  41. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.d.ts +2 -0
  42. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +95 -0
  43. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.d.ts +6 -0
  44. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.js +16 -0
  45. package/dist/components/questions/types/match-word-to-picture-group/index.d.ts +5 -0
  46. package/dist/components/questions/types/match-word-to-picture-group/index.js +5 -0
  47. package/dist/components/questions/types/match-word-to-picture-group/map-match-word-to-picture-group-data.d.ts +4 -0
  48. package/dist/components/questions/types/match-word-to-picture-group/map-match-word-to-picture-group-data.js +77 -0
  49. package/dist/components/questions/types/match-word-to-picture-group/register.d.ts +2 -0
  50. package/dist/components/questions/types/match-word-to-picture-group/register.js +36 -0
  51. package/dist/components/questions/types/match-word-to-picture-group/transform.d.ts +2 -0
  52. package/dist/components/questions/types/match-word-to-picture-group/transform.js +36 -0
  53. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.d.ts +2 -0
  54. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +25 -0
  55. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.d.ts +2 -0
  56. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.js +106 -0
  57. package/dist/components/questions/types/word-ordering-group/index.d.ts +5 -0
  58. package/dist/components/questions/types/word-ordering-group/index.js +5 -0
  59. package/dist/components/questions/types/word-ordering-group/map-word-ordering-group-data.d.ts +4 -0
  60. package/dist/components/questions/types/word-ordering-group/map-word-ordering-group-data.js +74 -0
  61. package/dist/components/questions/types/word-ordering-group/register.d.ts +2 -0
  62. package/dist/components/questions/types/word-ordering-group/register.js +36 -0
  63. package/dist/components/questions/types/word-ordering-group/transform.d.ts +2 -0
  64. package/dist/components/questions/types/word-ordering-group/transform.js +33 -0
  65. package/dist/components/questions/viewer/MatchColumnsToMakeSentencesViewer.d.ts +8 -0
  66. package/dist/components/questions/viewer/MatchColumnsToMakeSentencesViewer.js +49 -0
  67. package/dist/components/questions/viewer/MatchWordToPictureGroupViewer.js +2 -2
  68. package/dist/components/questions/viewer/QuestionViewer.js +6 -0
  69. package/dist/components/questions/viewer/WordOrderingGroupViewer.d.ts +7 -0
  70. package/dist/components/questions/viewer/WordOrderingGroupViewer.js +29 -0
  71. package/dist/components/questions/viewer/index.d.ts +2 -0
  72. package/dist/components/questions/viewer/index.js +2 -0
  73. package/dist/components/results/renderers/review-question-body-dedicated.js +1 -1
  74. package/dist/components/results/renderers/review-question-body-movers.js +6 -2
  75. package/dist/components/results/review-data.js +13 -0
  76. package/dist/index.d.ts +1 -1
  77. package/dist/index.js +1 -1
  78. package/dist/shared/constants/question-skills.js +6 -0
  79. package/dist/shared/lib/utils/question-reverse-transform.js +111 -2
  80. package/dist/shared/lib/utils/question-transform.js +6 -0
  81. package/dist/shared/types/common.types.d.ts +1 -1
  82. package/dist/shared/types/questions/choose-the-correct-answer-group.d.ts +1 -0
  83. package/dist/shared/types/questions/index.d.ts +3 -0
  84. package/dist/shared/types/questions/index.js +6 -0
  85. package/dist/shared/types/questions/match-columns-to-make-sentences.d.ts +50 -0
  86. package/dist/shared/types/questions/match-columns-to-make-sentences.js +1 -0
  87. package/dist/shared/types/questions/match-word-to-picture-group.d.ts +31 -0
  88. package/dist/shared/types/questions/match-word-to-picture-group.js +1 -0
  89. package/dist/shared/types/questions/word-ordering-group.d.ts +28 -0
  90. package/dist/shared/types/questions/word-ordering-group.js +1 -0
  91. package/package.json +1 -1
@@ -0,0 +1,4 @@
1
+ import { type WordOrderingGroupData, type WordOrderingGroupItemData } from '../../_shared/types/word-ordering-group.type';
2
+ export declare function createEmptyWordOrderingGroupItem(questionNumber: number): WordOrderingGroupItemData;
3
+ export declare function mapWordOrderingGroupItem(item: unknown, index: number, fallbackAnswer?: unknown): WordOrderingGroupItemData;
4
+ export declare function mapQuestionToWordOrderingGroupData(question: Record<string, unknown> | null | undefined): WordOrderingGroupData;
@@ -0,0 +1,74 @@
1
+ import { WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION, } from '../../_shared/types/word-ordering-group.type';
2
+ function isRecord(value) {
3
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
4
+ }
5
+ function asRecord(value) {
6
+ return isRecord(value) ? value : {};
7
+ }
8
+ function asString(value, fallback = '') {
9
+ return typeof value === 'string' ? value : fallback;
10
+ }
11
+ function asStringList(value) {
12
+ if (!Array.isArray(value))
13
+ return [];
14
+ return value
15
+ .map((item) => (typeof item === 'string' ? item.trim() : ''))
16
+ .filter(Boolean);
17
+ }
18
+ export function createEmptyWordOrderingGroupItem(questionNumber) {
19
+ return {
20
+ words: ['', ''],
21
+ answer: ['', ''],
22
+ isExample: false,
23
+ points: 1,
24
+ questionNumber,
25
+ };
26
+ }
27
+ export function mapWordOrderingGroupItem(item, index, fallbackAnswer) {
28
+ const record = asRecord(item);
29
+ const content = asRecord(record.content);
30
+ const correctAnswer = asRecord(record.correctAnswer);
31
+ const isExample = record.isExample === true;
32
+ const words = asStringList(content.words).length
33
+ ? asStringList(content.words)
34
+ : asStringList(record.words);
35
+ const answer = asStringList(correctAnswer.answer).length
36
+ ? asStringList(correctAnswer.answer)
37
+ : asStringList(record.answer).length
38
+ ? asStringList(record.answer)
39
+ : asStringList(fallbackAnswer);
40
+ return {
41
+ words,
42
+ answer,
43
+ isExample,
44
+ points: isExample ? 0 : (typeof record.points === 'number' ? record.points : 1),
45
+ questionNumber: typeof record.questionNumber === 'number' ? record.questionNumber : index + 1,
46
+ };
47
+ }
48
+ export function mapQuestionToWordOrderingGroupData(question) {
49
+ const source = asRecord(question);
50
+ const content = asRecord(source.content);
51
+ const meta = asRecord(content.meta);
52
+ const answer = asRecord(source.answer);
53
+ const correctAnswer = asRecord(source.correctAnswer ?? source.correct_answer ?? answer.correctAnswer);
54
+ const explanation = asString(source.explanation) || asString(answer.explanation);
55
+ const fallbackAnswers = Array.isArray(correctAnswer.answer) ? correctAnswer.answer : [];
56
+ if (Array.isArray(content.items) || Array.isArray(answer.items)) {
57
+ const rawItems = Array.isArray(content.items) ? content.items : answer.items;
58
+ const items = rawItems.map((item, index) => mapWordOrderingGroupItem(item, index, fallbackAnswers[index]));
59
+ return {
60
+ instruction: asString(meta.instruction) ||
61
+ asString(answer.instruction) ||
62
+ WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION,
63
+ items,
64
+ explanation,
65
+ points: items.reduce((total, item) => (item.isExample ? total : total + (item.points || 0)), 0),
66
+ };
67
+ }
68
+ return {
69
+ instruction: WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION,
70
+ items: [createEmptyWordOrderingGroupItem(1)],
71
+ explanation,
72
+ points: 1,
73
+ };
74
+ }
@@ -0,0 +1,2 @@
1
+ import type { QuestionTypeRegistration } from '../../creator/question-type-registry';
2
+ export declare const wordOrderingGroupRegistration: QuestionTypeRegistration;
@@ -0,0 +1,36 @@
1
+ import { WordOrderingGroupCreator } from './WordOrderingGroupCreator';
2
+ import { mapQuestionToWordOrderingGroupData } from './map-word-ordering-group-data';
3
+ export const wordOrderingGroupRegistration = {
4
+ component: WordOrderingGroupCreator,
5
+ transformInitialData: (initialData) => mapQuestionToWordOrderingGroupData(initialData),
6
+ getExtraProps: (ctx) => ({
7
+ onUnsavedChangesChange: ctx.onUnsavedChangesChange,
8
+ validationRef: ctx.validationRef,
9
+ }),
10
+ wrapOnSave: (data, ctx) => {
11
+ const payload = data;
12
+ return {
13
+ type: ctx.questionType,
14
+ content: ctx.state.content,
15
+ points: payload.points ?? ctx.state.points ?? 1,
16
+ level: ctx.state.level,
17
+ difficulty: ctx.state.difficulty,
18
+ skill: ctx.state.skill,
19
+ answer: payload,
20
+ explanation: payload.explanation || '',
21
+ };
22
+ },
23
+ wrapOnChange: (data, ctx) => {
24
+ const payload = data;
25
+ return {
26
+ type: ctx.questionType,
27
+ content: ctx.state.content,
28
+ points: payload.points ?? ctx.state.points ?? 1,
29
+ level: ctx.state.level,
30
+ difficulty: ctx.state.difficulty,
31
+ skill: ctx.state.skill,
32
+ answer: payload,
33
+ explanation: payload.explanation || '',
34
+ };
35
+ },
36
+ };
@@ -0,0 +1,2 @@
1
+ import type { TransformHandler } from '../../../../shared/lib/utils/question-transform-types';
2
+ export declare const transformWordOrderingGroup: TransformHandler;
@@ -0,0 +1,33 @@
1
+ import { WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION, WORD_ORDERING_ITEM_TYPE, } from '../../_shared/types/word-ordering-group.type';
2
+ function tokens(value) {
3
+ if (!Array.isArray(value))
4
+ return [];
5
+ return value
6
+ .filter((item) => typeof item === 'string')
7
+ .map((item) => item.trim())
8
+ .filter(Boolean);
9
+ }
10
+ export const transformWordOrderingGroup = (question) => {
11
+ const answerData = question.answer;
12
+ const items = Array.isArray(answerData?.items) ? answerData.items : [];
13
+ const apiItems = items.map((item, index) => ({
14
+ questionType: WORD_ORDERING_ITEM_TYPE,
15
+ ...(item.isExample ? { isExample: true } : {}),
16
+ content: { words: tokens(item.words) },
17
+ correctAnswer: { answer: tokens(item.answer) },
18
+ points: item.isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
19
+ questionNumber: item.questionNumber || index + 1,
20
+ }));
21
+ return {
22
+ apiContent: {
23
+ meta: {
24
+ instruction: answerData?.instruction || WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION,
25
+ },
26
+ items: apiItems,
27
+ },
28
+ apiCorrectAnswer: {
29
+ answer: apiItems.map((item) => item.correctAnswer.answer),
30
+ },
31
+ totalPoints: items.reduce((total, item) => (item.isExample ? total : total + (typeof item.points === 'number' ? item.points : 1)), 0),
32
+ };
33
+ };
@@ -0,0 +1,8 @@
1
+ import type { Question } from '../../../shared/types/entities/question.types';
2
+ import type { MatchColumnsStudentItemAnswer } from '../_shared/types/match-columns-to-make-sentences.type';
3
+ export declare function MatchColumnsToMakeSentencesViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
4
+ question: Question;
5
+ isReviewMode?: boolean;
6
+ userAnswer?: unknown;
7
+ onSubmitAnswer?: (answer: MatchColumnsStudentItemAnswer[]) => void;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,49 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { MatchColumnsToMakeSentencesClient } from '../types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient';
5
+ import { mapQuestionToMatchColumnsGroupData } from '../types/match-columns-to-make-sentences/map-match-columns-to-make-sentences-data';
6
+ function isRecord(value) {
7
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
8
+ }
9
+ function parseAnswers(value, length) {
10
+ const source = Array.isArray(value)
11
+ ? value
12
+ : isRecord(value) && Array.isArray(value.answer)
13
+ ? value.answer
14
+ : null;
15
+ if (!source)
16
+ return undefined;
17
+ return Array.from({ length }, (_, index) => {
18
+ const raw = source[index];
19
+ if (!isRecord(raw))
20
+ return { fragmentIds: [], sentence: '' };
21
+ return {
22
+ fragmentIds: Array.isArray(raw.fragmentIds)
23
+ ? raw.fragmentIds.filter((id) => typeof id === 'string')
24
+ : [],
25
+ sentence: typeof raw.sentence === 'string' ? raw.sentence : '',
26
+ };
27
+ });
28
+ }
29
+ export function MatchColumnsToMakeSentencesViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
30
+ const questionData = mapQuestionToMatchColumnsGroupData(question);
31
+ const itemCount = questionData.items.length;
32
+ const parsedUser = parseAnswers(userAnswer, itemCount);
33
+ const questionKey = `${question.id}:${itemCount}`;
34
+ const [cachedKey, setCachedKey] = useState(questionKey);
35
+ const [localAnswers, setLocalAnswers] = useState(() => parsedUser ?? questionData.items.map((item) => (item.isExample
36
+ ? { fragmentIds: item.fragmentIds, sentence: item.sentence }
37
+ : { fragmentIds: [item.stemFragmentId], sentence: '' })));
38
+ if (cachedKey !== questionKey) {
39
+ setCachedKey(questionKey);
40
+ setLocalAnswers(parsedUser ?? questionData.items.map((item) => (item.isExample
41
+ ? { fragmentIds: item.fragmentIds, sentence: item.sentence }
42
+ : { fragmentIds: [item.stemFragmentId], sentence: '' })));
43
+ }
44
+ const userAnswers = parsedUser ?? localAnswers;
45
+ return (_jsx(MatchColumnsToMakeSentencesClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswerChange: isReviewMode ? undefined : (next) => {
46
+ setLocalAnswers(next);
47
+ onSubmitAnswer?.(next);
48
+ } }));
49
+ }
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { MatchWordToPictureGroupClient } from '../types/match-word-to-picture/MatchWordToPictureGroupClient';
4
- import { mapQuestionToMatchWordToPictureGroupData } from '../types/match-word-to-picture/map-match-word-to-picture-group-data';
3
+ import { MatchWordToPictureGroupClient } from '../types/match-word-to-picture-group/MatchWordToPictureGroupClient';
4
+ import { mapQuestionToMatchWordToPictureGroupData } from '../types/match-word-to-picture-group/map-match-word-to-picture-group-data';
5
5
  export function MatchWordToPictureGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
6
6
  const questionData = mapQuestionToMatchWordToPictureGroupData(question);
7
7
  const reviewAnswers = isReviewMode && !userAnswer
@@ -34,6 +34,8 @@ import { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
34
34
  import { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
35
35
  import { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
36
36
  import { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
37
+ import { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
38
+ import { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
37
39
  export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false, userAnswer, }) {
38
40
  const [selectedAnswer, setSelectedAnswer] = useState(userAnswer || null);
39
41
  const [playCount, setPlayCount] = useState(0);
@@ -641,6 +643,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
641
643
  return (_jsx(FillInBlankGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
642
644
  case 'MATCHING_WITH_LINES_GROUP':
643
645
  return (_jsx(MatchingWithLinesGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
646
+ case 'MATCH_COLUMNS_TO_MAKE_SENTENCES':
647
+ return (_jsx(MatchColumnsToMakeSentencesViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
644
648
  case 'CROSS_OUT_WORD_GROUP':
645
649
  return (_jsx(CrossOutWordGroupViewer, { question: question, isReviewMode: isReviewMode }));
646
650
  case 'ANSWER_THE_QUESTION_GROUP':
@@ -650,6 +654,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
650
654
  case 'WORD_ORDER_AND_MATCH_GROUP':
651
655
  case 'WORD_ORDER_AND_MATCH':
652
656
  return (_jsx(WordOrderAndMatchGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
657
+ case 'WORD_ORDERING_GROUP':
658
+ return (_jsx(WordOrderingGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
653
659
  case 'MATCH_WORD_TO_PICTURE_GROUP':
654
660
  return (_jsx(MatchWordToPictureGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
655
661
  case 'MATCH_WORD_TO_PICTURE':
@@ -0,0 +1,7 @@
1
+ import type { Question } from '../../../shared/types/entities/question.types';
2
+ export declare function WordOrderingGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
3
+ question: Question;
4
+ isReviewMode?: boolean;
5
+ userAnswer?: unknown;
6
+ onSubmitAnswer?: (answer: string[][]) => void;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { WordOrderingGroupClient } from '../types/word-ordering-group/WordOrderingGroupClient';
5
+ import { mapQuestionToWordOrderingGroupData } from '../types/word-ordering-group/map-word-ordering-group-data';
6
+ function isRecord(value) {
7
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
8
+ }
9
+ function parseAnswers(value, length) {
10
+ const source = Array.isArray(value)
11
+ ? value
12
+ : isRecord(value) && Array.isArray(value.answer)
13
+ ? value.answer
14
+ : null;
15
+ if (!source)
16
+ return undefined;
17
+ return Array.from({ length }, (_, index) => Array.isArray(source[index])
18
+ ? source[index].filter((token) => typeof token === 'string')
19
+ : []);
20
+ }
21
+ export function WordOrderingGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
22
+ const questionData = mapQuestionToWordOrderingGroupData(question);
23
+ const parsedUser = parseAnswers(userAnswer, questionData.items.length);
24
+ const [localAnswers, setLocalAnswers] = useState(() => parsedUser ?? questionData.items.map((item) => (item.isExample ? item.answer : [])));
25
+ return (_jsx(WordOrderingGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: parsedUser ?? localAnswers, onAnswerChange: isReviewMode ? undefined : (next) => {
26
+ setLocalAnswers(next);
27
+ onSubmitAnswer?.(next);
28
+ } }));
29
+ }
@@ -12,3 +12,5 @@ export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
12
12
  export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
13
13
  export { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
14
14
  export { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
15
+ export { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
16
+ export { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
@@ -12,3 +12,5 @@ export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
12
12
  export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
13
13
  export { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
14
14
  export { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
15
+ export { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
16
+ export { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
@@ -40,7 +40,7 @@ export function renderDedicatedReviewBody(ctx) {
40
40
  }
41
41
  case 'CHOOSE_THE_CORRECT_ANSWER_GROUP': {
42
42
  const data = transformChooseTheCorrectAnswerGroup(apiQuestions);
43
- return (_jsx(ReviewChooseBestAnswerRenderer, { partNumber: partNo, questionCount: questionCount, partName: part.name, instruction: part.instructions || data.instruction, audioUrl: data.audioUrl, questions: data.questions, answers: answers, isCorrectMap: isCorrectMap, correctOptionIdMap: correctOptionIdMap, example: data.example }));
43
+ return (_jsx(ReviewChooseBestAnswerRenderer, { partNumber: partNo, questionCount: questionCount, partName: part.name, instruction: part.instructions || data.instruction, audioUrl: data.audioUrl, imageUrl: data.imageUrl, questions: data.questions, answers: answers, isCorrectMap: isCorrectMap, correctOptionIdMap: correctOptionIdMap, example: data.example }));
44
44
  }
45
45
  case 'CHOOSE_CORRECT_ADJECTIVE': {
46
46
  const data = transformChooseCorrectAdjective(apiQuestions);
@@ -4,8 +4,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
4
4
  * Movers fallback review cases (isReviewMode=true).
5
5
  * Source: oe-exam-fe ReviewQuestionRenderer.tsx
6
6
  */
7
- import { MoversAnswerTheQuestionRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, } from '../../../components/exams/take/components/question-renderers';
8
- import { transformAnswerTheQuestion, transformFillWordHint, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformMatchingWithLinesGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, } from '../../../components/exams/take/utils/question-transformers';
7
+ import { MoversAnswerTheQuestionRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWordOrderingGroupRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, } from '../../../components/exams/take/components/question-renderers';
8
+ import { transformAnswerTheQuestion, transformFillWordHint, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformMatchingWithLinesGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWordOrderingGroupTake, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, } from '../../../components/exams/take/utils/question-transformers';
9
9
  import { normalizeCrossOutWordGroupAnswer, transformCrossOutWordGroup, } from '../../../components/exams/take/utils/cross-out-word-group.transformer';
10
10
  function asApiQuestions(questions) {
11
11
  return questions;
@@ -189,6 +189,10 @@ export function renderMoversReviewBody(ctx) {
189
189
  const data = transformMatchWordToPicture(apiQuestions);
190
190
  return (_jsx(MoversMatchWordToPictureRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, wordBank: data.wordBank, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerTextMap: correctAnswerTextMap, isCorrectMap: isCorrectMap }));
191
191
  }
192
+ case 'WORD_ORDERING_GROUP': {
193
+ const data = transformWordOrderingGroupTake(apiQuestions);
194
+ return (_jsx(MoversWordOrderingGroupRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: data.questions.filter((q) => !q.isExample).length, data: data, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true }));
195
+ }
192
196
  case 'WORD_ORDERING': {
193
197
  const data = transformWordOrdering(apiQuestions);
194
198
  return (_jsx(MoversWordOrderingRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, rawCorrectAnswerMap: rawCorrectAnswerMap, isCorrectMap: isCorrectMap }));
@@ -231,6 +231,19 @@ function transformStudentAnswer(questionId, questionType, value) {
231
231
  return [`${questionId}-item-${index}`, { answers: [] }];
232
232
  }));
233
233
  }
234
+ if (questionType === 'WORD_ORDERING_GROUP') {
235
+ const answer = Array.isArray(record.answer)
236
+ ? record.answer
237
+ : Array.isArray(value)
238
+ ? value
239
+ : [];
240
+ return Object.fromEntries(answer.map((item, index) => [
241
+ `${questionId}-item-${index}`,
242
+ Array.isArray(item)
243
+ ? item.filter((token) => typeof token === 'string')
244
+ : [],
245
+ ]));
246
+ }
234
247
  if (questionType === 'MATCHING_WITH_LINES_GROUP') {
235
248
  const answer = Array.isArray(record.answer)
236
249
  ? record.answer
package/dist/index.d.ts CHANGED
@@ -26,4 +26,4 @@ export { validateQuestion } from './components/questions/_shared/utils/question-
26
26
  export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
27
27
  export type { QuestionFormState } from './components/questions/_shared/hooks/useQuestionFormState';
28
28
  export * from './components/questions/_shared/types';
29
- export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, } from './components/questions/viewer';
29
+ export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, } from './components/questions/viewer';
package/dist/index.js CHANGED
@@ -21,4 +21,4 @@ export { QUESTION_TYPE_CONFIG, QUESTION_TYPES_WITH_DEDICATED_COMPONENTS } from '
21
21
  export { validateQuestion } from './components/questions/_shared/utils/question-validation';
22
22
  export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
23
23
  export * from './components/questions/_shared/types';
24
- export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, } from './components/questions/viewer';
24
+ export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, } from './components/questions/viewer';
@@ -84,6 +84,9 @@ export const QUESTION_TYPE_SKILLS = {
84
84
  'WORD_FILL_STRUCTURED_FORM': SkillEnum.READING,
85
85
  'CROSSWORD_PUZZLE': SkillEnum.READING,
86
86
  'FIND_WORDS_IN_MATRIX': SkillEnum.READING,
87
+ 'MATCH_COLUMNS_TO_MAKE_SENTENCES': SkillEnum.READING,
88
+ 'MATCH_WORD_TO_PICTURE_GROUP': SkillEnum.READING,
89
+ 'WORD_ORDERING_GROUP': SkillEnum.READING,
87
90
  // ========================================
88
91
  // WRITING SKILLS (Viết)
89
92
  // ========================================
@@ -216,6 +219,9 @@ export const QUESTION_TYPE_LABELS = {
216
219
  'WORD_FILL_STRUCTURED_FORM': 'Điền từ vào biểu mẫu có cấu trúc',
217
220
  'CROSSWORD_PUZZLE': 'Giải ô chữ (Crossword)',
218
221
  'FIND_WORDS_IN_MATRIX': 'Tìm từ trong ma trận (Word Search)',
222
+ 'MATCH_COLUMNS_TO_MAKE_SENTENCES': 'Ghép cột thành câu',
223
+ 'MATCH_WORD_TO_PICTURE_GROUP': 'Nối từ với tranh (nhóm)',
224
+ 'WORD_ORDERING_GROUP': 'Sắp xếp từ (nhóm)',
219
225
  // ========================================
220
226
  // WRITING SKILLS (Viết)
221
227
  // ========================================
@@ -226,7 +226,9 @@ function normalizeChooseAnswerGroupImageUrl(value) {
226
226
  const reverseChooseTheCorrectAnswerGroup = (apiQuestion) => {
227
227
  const content = asRecord(apiQuestion.content);
228
228
  const answer = readAnswer(apiQuestion);
229
- const fallbackAnswers = asArray(answer.answer).map((item) => asString(item));
229
+ const fallbackAnswers = asArray(answer.answer).map((item) => (asString(item) || (Array.isArray(item)
230
+ ? asString(item.find((token) => typeof token === 'string' && token.trim() !== ''))
231
+ : '')));
230
232
  const items = asArray(content.items).map((rawItem, index) => {
231
233
  const item = asRecord(rawItem);
232
234
  const itemContent = asRecord(item.content);
@@ -251,7 +253,13 @@ const reverseChooseTheCorrectAnswerGroup = (apiQuestion) => {
251
253
  { id: 'opt-2', text: '' },
252
254
  { id: 'opt-3', text: '' },
253
255
  ],
254
- correctAnswer: asString(itemAnswer.answer) || fallbackAnswers[index] || options[0]?.id || 'opt-1',
256
+ correctAnswer: asString(itemAnswer.answer)
257
+ || (Array.isArray(itemAnswer.answer)
258
+ ? asString(itemAnswer.answer.find((token) => typeof token === 'string' && token.trim() !== ''))
259
+ : '')
260
+ || fallbackAnswers[index]
261
+ || options[0]?.id
262
+ || 'opt-1',
255
263
  isExample,
256
264
  points: isExample ? 0 : asNumber(item.points, 1),
257
265
  questionNumber: asNumber(item.questionNumber, index + 1),
@@ -260,12 +268,14 @@ const reverseChooseTheCorrectAnswerGroup = (apiQuestion) => {
260
268
  };
261
269
  });
262
270
  const audioUrl = asString(asRecord(content.meta).audioUrl).trim();
271
+ const imageUrl = normalizeChooseAnswerGroupImageUrl(asRecord(content.meta).imageUrl);
263
272
  return {
264
273
  content: '',
265
274
  answer: {
266
275
  instruction: asString(asRecord(content.meta).instruction, 'Choose the correct answer.'),
267
276
  passage: asString(asRecord(content.meta).passage),
268
277
  ...(audioUrl ? { audioUrl } : {}),
278
+ ...(imageUrl !== undefined ? { imageUrl } : {}),
269
279
  items,
270
280
  explanation: asString(apiQuestion.explanation),
271
281
  points: items.reduce((total, item) => (item.isExample ? total : total + item.points), 0),
@@ -603,6 +613,102 @@ const reverseSimpleAnswer = (apiQuestion) => {
603
613
  },
604
614
  };
605
615
  };
616
+ const reverseWordOrderingGroup = (apiQuestion) => {
617
+ const content = asRecord(apiQuestion.content);
618
+ const answer = readAnswer(apiQuestion);
619
+ const fallbackAnswers = asArray(answer.answer);
620
+ const items = asArray(content.items);
621
+ const meta = asRecord(content.meta);
622
+ const mappedItems = items.map((rawItem, index) => {
623
+ const item = asRecord(rawItem);
624
+ const itemContent = asRecord(item.content);
625
+ const itemAnswer = asRecord(item.correctAnswer);
626
+ const isExample = item.isExample === true;
627
+ const words = asArray(itemContent.words).map((token) => asString(token)).filter(Boolean);
628
+ const itemTokens = asArray(itemAnswer.answer).map((token) => asString(token)).filter(Boolean);
629
+ const fallbackTokens = asArray(fallbackAnswers[index]).map((token) => asString(token)).filter(Boolean);
630
+ return {
631
+ words,
632
+ answer: itemTokens.length ? itemTokens : fallbackTokens,
633
+ isExample,
634
+ points: isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
635
+ questionNumber: typeof item.questionNumber === 'number' ? item.questionNumber : index + 1,
636
+ };
637
+ });
638
+ return {
639
+ content: '',
640
+ answer: {
641
+ instruction: asString(meta.instruction, 'Order the words.'),
642
+ items: mappedItems,
643
+ explanation: asString(apiQuestion.explanation),
644
+ points: mappedItems.reduce((total, item) => (item.isExample ? total : total + (item.points || 0)), 0),
645
+ },
646
+ };
647
+ };
648
+ const reverseMatchWordToPictureGroup = (apiQuestion) => {
649
+ const content = asRecord(apiQuestion.content);
650
+ const answer = readAnswer(apiQuestion);
651
+ const fallbackAnswers = asArray(answer.answer);
652
+ const items = asArray(content.items);
653
+ const meta = asRecord(content.meta);
654
+ const mappedItems = items.map((rawItem, index) => {
655
+ const item = asRecord(rawItem);
656
+ const itemContent = asRecord(item.content);
657
+ const itemAnswer = asRecord(item.correctAnswer);
658
+ const isExample = item.isExample === true;
659
+ return {
660
+ imageUrl: asString(itemContent.imageUrl),
661
+ correctAnswer: asString(itemAnswer.answer) || asString(fallbackAnswers[index]),
662
+ isExample,
663
+ points: isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
664
+ questionNumber: typeof item.questionNumber === 'number' ? item.questionNumber : index + 1,
665
+ };
666
+ });
667
+ return {
668
+ content: '',
669
+ answer: {
670
+ instruction: asString(meta.instruction, 'Look and complete.'),
671
+ wordBank: asArray(meta.wordBank).map((word) => asString(word)).filter(Boolean),
672
+ items: mappedItems,
673
+ explanation: asString(apiQuestion.explanation),
674
+ points: mappedItems.reduce((total, item) => (item.isExample ? total : total + (item.points || 0)), 0),
675
+ },
676
+ };
677
+ };
678
+ const reverseMatchColumnsToMakeSentences = (apiQuestion) => {
679
+ const content = asRecord(apiQuestion.content);
680
+ const answer = readAnswer(apiQuestion);
681
+ const fallbackAnswers = asArray(answer.answer);
682
+ const items = asArray(content.items);
683
+ const columns = asArray(content.columns);
684
+ const meta = asRecord(content.meta);
685
+ const mappedItems = items.map((rawItem, index) => {
686
+ const item = asRecord(rawItem);
687
+ const itemContent = asRecord(item.content);
688
+ const itemAnswer = asRecord(asRecord(item.correctAnswer).answer ?? fallbackAnswers[index]);
689
+ const fragmentIds = asArray(itemAnswer.fragmentIds).map((id) => asString(id)).filter(Boolean);
690
+ const isExample = item.isExample === true;
691
+ return {
692
+ stemFragmentId: asString(itemContent.stemFragmentId) || fragmentIds[0] || '',
693
+ fragmentIds,
694
+ sentence: asString(itemAnswer.sentence),
695
+ isExample,
696
+ points: isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
697
+ questionNumber: typeof item.questionNumber === 'number' ? item.questionNumber : index + 1,
698
+ };
699
+ });
700
+ return {
701
+ content: '',
702
+ answer: {
703
+ title: asString(meta.title),
704
+ instruction: asString(meta.instruction, 'Match the information to make sentences. You can use the same words more than once.'),
705
+ columns,
706
+ items: mappedItems,
707
+ explanation: asString(apiQuestion.explanation),
708
+ points: mappedItems.reduce((total, item) => (item.isExample ? total : total + (item.points || 0)), 0),
709
+ },
710
+ };
711
+ };
606
712
  const handlers = {
607
713
  CHOOSE_THE_CORRECT_ANSWER: reverseChooseTheCorrectAnswer,
608
714
  CHOOSE_THE_CORRECT_ANSWER_GROUP: reverseChooseTheCorrectAnswerGroup,
@@ -653,6 +759,9 @@ const handlers = {
653
759
  GN_SPEAKING_INTERVIEW: reverseGroupPayload,
654
760
  CROSSWORD_PUZZLE: reverseCrosswordPuzzle,
655
761
  FIND_WORDS_IN_MATRIX: reverseFindWordsInMatrix,
762
+ MATCH_COLUMNS_TO_MAKE_SENTENCES: reverseMatchColumnsToMakeSentences,
763
+ MATCH_WORD_TO_PICTURE_GROUP: reverseMatchWordToPictureGroup,
764
+ WORD_ORDERING_GROUP: reverseWordOrderingGroup,
656
765
  };
657
766
  function reverseWriteAShortLetter(apiQuestion) {
658
767
  const content = asRecord(apiQuestion.content);
@@ -47,6 +47,9 @@ import { transformGNSpeakingInterview } from '../../../components/questions/type
47
47
  import { transformWordFillStructuredForm } from '../../../components/questions/types/word-fill-structured-form/transform';
48
48
  import { transformCrosswordPuzzle } from '../../../components/questions/types/crossword-puzzle/transform';
49
49
  import { transformFindWordsInMatrix } from '../../../components/questions/types/find-words-in-matrix/transform';
50
+ import { transformMatchColumnsToMakeSentences } from '../../../components/questions/types/match-columns-to-make-sentences/transform';
51
+ import { transformMatchWordToPictureGroup } from '../../../components/questions/types/match-word-to-picture-group/transform';
52
+ import { transformWordOrderingGroup } from '../../../components/questions/types/word-ordering-group/transform';
50
53
  // ─── Handler map ────────────────────────────────────────────────────────────
51
54
  const questionTransformHandlers = {
52
55
  // Look picture
@@ -100,6 +103,9 @@ const questionTransformHandlers = {
100
103
  WORD_FILL_STRUCTURED_FORM: transformWordFillStructuredForm,
101
104
  CROSSWORD_PUZZLE: transformCrosswordPuzzle,
102
105
  FIND_WORDS_IN_MATRIX: transformFindWordsInMatrix,
106
+ MATCH_COLUMNS_TO_MAKE_SENTENCES: transformMatchColumnsToMakeSentences,
107
+ MATCH_WORD_TO_PICTURE_GROUP: transformMatchWordToPictureGroup,
108
+ WORD_ORDERING_GROUP: transformWordOrderingGroup,
103
109
  };
104
110
  /**
105
111
  * Transform frontend question format to API format.
@@ -29,7 +29,7 @@ export declare enum StudentSelectionType {
29
29
  }
30
30
  export type ResultStatus = 'PENDING' | 'PARTIAL_GRADED' | 'GRADED';
31
31
  export type StudentStatus = 'NOT_STARTED' | 'IN_PROGRESS' | 'SUBMITTED';
32
- export type QuestionType = 'FILL_IN_BLANK' | 'TRUE_FALSE' | 'MATCHING' | 'ORDERING' | 'LISTENING' | 'COLORING' | 'ESSAY' | 'LISTEN_AND_FILL_IN_THE_BLANK_WITH_IMAGE' | 'LISTEN_AND_TICK_ANSWER' | 'LISTEN_AND_FILL_NAME_NUMBER' | 'LISTENING_FILL_BLANK' | 'LISTENING_COLOR' | 'TICK_TRUE_OR_FALSE' | 'TICK_YES_OR_NO' | 'READING_TICK_CROSS' | 'ARRANGE_LETTERS_INTO_WORDS' | 'READ_PASSAGE_AND_COMPLETE_STORY' | 'CLOZE_TEST_WITH_TICK_BOX' | 'READ_AND_CHOOSE_APPROPRIATE_WORD' | 'WRITE_SHORT_PARAGRAPH' | 'LABEL_THE_PICTURE' | 'READING_LETTER_ARRANGE' | 'READING_PASSAGE_FILL' | 'FILL_BLANK' | 'FILL_MISSING_WORDS_IN_GRID' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'LABEL_THE_PICTURE' | 'READ_AND_COLOR_OBJECTS' | 'IMAGE_OBJECT_MATCHING' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'FILL_IN_BLANK_GROUP' | 'MATCHING_WITH_LINES_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'SPONTANEOUS_QA_GROUP' | 'WORD_ORDER_AND_MATCH_GROUP' | 'WRITE_A_SHORT_LETTER' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_CORRECT_ADJECTIVE' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX';
32
+ export type QuestionType = 'FILL_IN_BLANK' | 'TRUE_FALSE' | 'MATCHING' | 'ORDERING' | 'LISTENING' | 'COLORING' | 'ESSAY' | 'LISTEN_AND_FILL_IN_THE_BLANK_WITH_IMAGE' | 'LISTEN_AND_TICK_ANSWER' | 'LISTEN_AND_FILL_NAME_NUMBER' | 'LISTENING_FILL_BLANK' | 'LISTENING_COLOR' | 'TICK_TRUE_OR_FALSE' | 'TICK_YES_OR_NO' | 'READING_TICK_CROSS' | 'ARRANGE_LETTERS_INTO_WORDS' | 'READ_PASSAGE_AND_COMPLETE_STORY' | 'CLOZE_TEST_WITH_TICK_BOX' | 'READ_AND_CHOOSE_APPROPRIATE_WORD' | 'WRITE_SHORT_PARAGRAPH' | 'LABEL_THE_PICTURE' | 'READING_LETTER_ARRANGE' | 'READING_PASSAGE_FILL' | 'FILL_BLANK' | 'FILL_MISSING_WORDS_IN_GRID' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'LABEL_THE_PICTURE' | 'READ_AND_COLOR_OBJECTS' | 'IMAGE_OBJECT_MATCHING' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'FILL_IN_BLANK_GROUP' | 'MATCHING_WITH_LINES_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'SPONTANEOUS_QA_GROUP' | 'WORD_ORDER_AND_MATCH_GROUP' | 'WRITE_A_SHORT_LETTER' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_CORRECT_ADJECTIVE' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX' | 'MATCH_COLUMNS_TO_MAKE_SENTENCES' | 'MATCH_WORD_TO_PICTURE_GROUP' | 'WORD_ORDERING_GROUP';
33
33
  export type Level = 'Pre-A1' | 'A1' | 'A2' | 'B1' | 'B2' | 'C1' | 'C2';
34
34
  export type Difficulty = 'EASY' | 'MEDIUM' | 'HARD';
35
35
  export type Skill = 'LISTENING' | 'READING' | 'WRITING' | 'SPEAKING';
@@ -25,6 +25,7 @@ export interface ChooseTheCorrectAnswerGroupContent {
25
25
  instruction?: string;
26
26
  passage?: string;
27
27
  audioUrl?: string;
28
+ imageUrl?: string | string[];
28
29
  };
29
30
  items: ChooseTheCorrectAnswerGroupItem[];
30
31
  }
@@ -34,5 +34,8 @@ export * from './crossword-puzzle';
34
34
  export * from './find-words-in-matrix';
35
35
  export * from './match-word-to-picture';
36
36
  export * from './matching-with-lines-group';
37
+ export * from './match-columns-to-make-sentences';
38
+ export * from './match-word-to-picture-group';
39
+ export * from './word-ordering-group';
37
40
  export * from './true-false-correct-group';
38
41
  export * from './fill-in-blank-group';