diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 8df8bed5..2e6c67de 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -32,6 +32,7 @@ declare module 'vue' { Categories: typeof import('./src/components/Settings/Categories.vue')['default'] CertificationLinks: typeof import('./src/components/CertificationLinks.vue')['default'] ChapterModal: typeof import('./src/components/Modals/ChapterModal.vue')['default'] + Code: typeof import('./src/components/Controls/Code.vue')['default'] CodeEditor: typeof import('./src/components/Controls/CodeEditor.vue')['default'] CollapseSidebar: typeof import('./src/components/Icons/CollapseSidebar.vue')['default'] CourseCard: typeof import('./src/components/CourseCard.vue')['default'] diff --git a/frontend/package.json b/frontend/package.json index bb7a91da..a7df7298 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,10 @@ "copy-html-entry": "cp ../lms/public/frontend/index.html ../lms/www/lms.html" }, "dependencies": { + "@codemirror/lang-html": "^6.4.9", + "@codemirror/lang-javascript": "^6.2.4", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/lang-python": "^6.2.1", "@editorjs/checklist": "^1.6.0", "@editorjs/code": "^2.9.0", "@editorjs/editorjs": "^2.29.0", @@ -24,7 +28,7 @@ "ace-builds": "^1.36.2", "apexcharts": "^4.3.0", "chart.js": "^4.4.1", - "codemirror-editor-vue3": "^2.8.0", + "codemirror": "^6.0.1", "dayjs": "^1.11.6", "feather-icons": "^4.28.0", "frappe-ui": "^0.1.147", @@ -35,9 +39,11 @@ "plyr": "^3.7.8", "socket.io-client": "^4.7.2", "tailwindcss": "3.4.15", + "thememirror": "^2.0.1", "typescript": "^5.7.2", "vue": "^3.4.23", "vue-chartjs": "^5.3.0", + "vue-codemirror": "^6.1.1", "vue-draggable-next": "^2.2.1", "vue-router": "^4.0.12", "vue3-apexcharts": "^1.8.0", diff --git a/frontend/src/components/Controls/Code.vue b/frontend/src/components/Controls/Code.vue new file mode 100644 index 00000000..a14060b8 --- /dev/null +++ b/frontend/src/components/Controls/Code.vue @@ -0,0 +1,156 @@ + + + + + {{ __('Save') }} + + + + + diff --git a/frontend/src/index.css b/frontend/src/index.css index 166ac595..92b6d76d 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,2 +1,3 @@ @import './assets/Inter/inter.css'; @import 'frappe-ui/src/style.css'; +@import './styles/codemirror.css'; \ No newline at end of file diff --git a/frontend/src/pages/ProgrammingExercise.vue b/frontend/src/pages/ProgrammingExercise.vue deleted file mode 100644 index d34b999f..00000000 --- a/frontend/src/pages/ProgrammingExercise.vue +++ /dev/null @@ -1,44 +0,0 @@ - - {{ exercise }} - Run Code - - diff --git a/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseForm.vue b/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseForm.vue new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue b/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue new file mode 100644 index 00000000..d3512ea0 --- /dev/null +++ b/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue @@ -0,0 +1,307 @@ + + + + + + + + + + {{ exercise.doc?.language }} + + + + {{ submission.doc.status }} + + + + + + {{ __('Run') }} + + + + + + + {{ __('Compiler Error') }}: + + + + + + + {{ __('Test Cases') }} + + + + + {{ __('Test {0}').format(index + 1) }} - + + {{ testCase.status }} + + + + + + + + + + + {{ __('Input') }}: + {{ testCase.input }} + + + + {{ __('Your Output') }} + + + {{ testCase.output }} + + + + + {{ __('Expected Output') }} + + {{ testCase.expected_output }} + + + + + + + + + + diff --git a/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmissions.vue b/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmissions.vue new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/pages/ProgrammingExercises/ProgrammingExercises.vue b/frontend/src/pages/ProgrammingExercises/ProgrammingExercises.vue new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/router.js b/frontend/src/router.js index 3bf8070d..63f6fb64 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -215,6 +215,37 @@ const routes = [ name: 'PersonaForm', component: () => import('@/pages/PersonaForm.vue'), }, + { + path: '/exercises', + name: 'ProgrammingExercises', + component: () => + import('@/pages/ProgrammingExercises/ProgrammingExercises.vue'), + }, + { + path: '/exercises/:exerciseID', + name: 'ProgrammingExerciseForm', + component: () => + import('@/pages/ProgrammingExercises/ProgrammingExerciseForm.vue'), + props: true, + }, + { + path: '/exercises/:exerciseID/submissions', + name: 'ProgrammingExerciseSubmissions', + component: () => + import( + '@/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue' + ), + props: true, + }, + { + path: '/exercises/:exerciseID/submission/:submissionID', + name: 'ProgrammingExerciseSubmission', + component: () => + import( + '@/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue' + ), + props: true, + }, ] let router = createRouter({ diff --git a/frontend/src/styles/codemirror.css b/frontend/src/styles/codemirror.css new file mode 100644 index 00000000..481e098b --- /dev/null +++ b/frontend/src/styles/codemirror.css @@ -0,0 +1,65 @@ +.cm-editor { + user-select: text; + padding: 0px !important; + position: relative !important; +} +.cm-gutters { + @apply !border-0 !bg-transparent !px-1.5 !text-xs !leading-6 !text-gray-500; +} +.cm-foldGutter span { + @apply !hidden !opacity-0; +} +.cm-gutterElement { + @apply !text-left; +} +.cm-activeLine { + @apply !bg-transparent; +} +.cm-activeLineGutter { + @apply !bg-transparent text-gray-600; +} +.cm-editor { + width: 100%; + user-select: text; +} +.cm-placeholder { + @apply !leading-6 !text-gray-500; +} +.cm-scroller { + @apply !font-mono !leading-6 !text-gray-600; +} +.cm-matchingBracket { + font-weight: 500 !important; + background: none !important; + border-bottom: 1px solid #000 !important; + outline: none !important; +} +.cm-focused { + outline: none !important; +} +.cm-tooltip-autocomplete { + @apply !rounded-lg !shadow-md !bg-surface-white !p-1.5 !border-none; +} +.cm-tooltip-autocomplete > ul { + font-family: 'Inter' !important; +} +.cm-tooltip-autocomplete ul li[aria-selected='true'] { + @apply !rounded !bg-gray-200/80; + color: #000 !important; +} +.cm-completionLabel { + margin-right: 1rem !important; +} +.cm-completionDetail { + margin-left: auto !important; +} +.inline-expression .cm-content { + padding: 0 !important; + line-height: 26px !important; +} +.inline-expression .cm-placeholder { + line-height: 26px !important; +} +.inline-expression .cm-gutters { + line-height: 26px !important; +} diff --git a/lms/lms/api.py b/lms/lms/api.py index c9ba5252..3a09567a 100644 --- a/lms/lms/api.py +++ b/lms/lms/api.py @@ -1493,3 +1493,75 @@ def update_meta_info(type, route, meta_tags): print(new_tag) new_tag.insert() print(new_tag.as_dict()) + + +@frappe.whitelist() +def create_programming_exercise_submission(exercise, submission, code, test_cases): + if submission == "new": + return make_new_exercise_submission(exercise, code, test_cases) + else: + update_exercise_submission(submission, code, test_cases) + + +def make_new_exercise_submission(exercise, code, test_cases): + submission = frappe.new_doc("LMS Programming Exercise Submission") + submission.exercise = exercise + submission.member = frappe.session.user + submission.code = code + + for test_case in test_cases: + submission.append( + "test_cases", + { + "input": test_case.get("input"), + "output": test_case.get("output"), + "expected_output": test_case.get("expected_output"), + "status": test_case.get("status", test_case.get("status", "Failed")), + }, + ) + + submission.status = get_exercise_status(test_cases) + submission.insert() + return submission.name + + +def update_exercise_submission(submission, code, test_cases): + update_test_cases(test_cases, submission) + status = get_exercise_status(test_cases) + frappe.db.set_value( + "LMS Programming Exercise Submission", submission, {"status": status, "code": code} + ) + + +def get_exercise_status(test_cases): + if not test_cases: + return "Failed" + + if all(row.get("status", "Failed") == "Passed" for row in test_cases): + return "Passed" + else: + return "Failed" + + +def update_test_cases(test_cases, submission): + for row in test_cases: + test_case = frappe.db.get_value( + "LMS Test Case Submission", + { + "parent": submission, + "input": row.get("input"), + }, + "name", + as_dict=True, + ) + + if test_case: + frappe.db.set_value( + "LMS Test Case Submission", + test_case.name, + { + "output": row.get("output"), + "expected_output": row.get("expected_output"), + "status": row.get("status", "Failed"), + }, + ) diff --git a/lms/lms/doctype/lms_programming_exercise/__init__.py b/lms/lms/doctype/lms_programming_exercise/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_programming_exercise/lms_programming_exercise.js b/lms/lms/doctype/lms_programming_exercise/lms_programming_exercise.js new file mode 100644 index 00000000..17375dca --- /dev/null +++ b/lms/lms/doctype/lms_programming_exercise/lms_programming_exercise.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("LMS Programming Exercise", { +// refresh(frm) { + +// }, +// }); diff --git a/lms/lms/doctype/lms_programming_exercise/lms_programming_exercise.json b/lms/lms/doctype/lms_programming_exercise/lms_programming_exercise.json new file mode 100644 index 00000000..7db24b50 --- /dev/null +++ b/lms/lms/doctype/lms_programming_exercise/lms_programming_exercise.json @@ -0,0 +1,91 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-06-18 15:02:36.198855", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "title", + "column_break_jlzi", + "language", + "section_break_tjwv", + "problem_statement", + "section_break_ftkh", + "test_cases" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "problem_statement", + "fieldtype": "Text Editor", + "in_list_view": 1, + "label": "Problem Statement", + "reqd": 1 + }, + { + "default": "Python", + "fieldname": "language", + "fieldtype": "Select", + "label": "Language", + "options": "Python\nJavaScript", + "reqd": 1 + }, + { + "fieldname": "column_break_jlzi", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_tjwv", + "fieldtype": "Section Break" + }, + { + "fieldname": "section_break_ftkh", + "fieldtype": "Section Break" + }, + { + "fieldname": "test_cases", + "fieldtype": "Table", + "label": "Test Cases", + "options": "LMS Test Case" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [ + { + "link_doctype": "LMS Programming Exercise Submission", + "link_fieldname": "exercise" + } + ], + "modified": "2025-06-19 14:17:35.200575", + "modified_by": "sayali@frappe.io", + "module": "LMS", + "name": "LMS Programming Exercise", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "show_title_field_in_link": 1, + "sort_field": "creation", + "sort_order": "DESC", + "states": [], + "title_field": "title" +} diff --git a/lms/lms/doctype/lms_programming_exercise/lms_programming_exercise.py b/lms/lms/doctype/lms_programming_exercise/lms_programming_exercise.py new file mode 100644 index 00000000..bec575da --- /dev/null +++ b/lms/lms/doctype/lms_programming_exercise/lms_programming_exercise.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LMSProgrammingExercise(Document): + pass diff --git a/lms/lms/doctype/lms_programming_exercise/test_lms_programming_exercise.py b/lms/lms/doctype/lms_programming_exercise/test_lms_programming_exercise.py new file mode 100644 index 00000000..6c4e6be0 --- /dev/null +++ b/lms/lms/doctype/lms_programming_exercise/test_lms_programming_exercise.py @@ -0,0 +1,30 @@ +# Copyright (c) 2025, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase, UnitTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + +class UnitTestLMSProgrammingExercise(UnitTestCase): + """ + Unit tests for LMSProgrammingExercise. + Use this class for testing individual functions and methods. + """ + + pass + + +class IntegrationTestLMSProgrammingExercise(IntegrationTestCase): + """ + Integration tests for LMSProgrammingExercise. + Use this class for testing interactions between multiple components. + """ + + pass diff --git a/lms/lms/doctype/lms_programming_exercise_submission/__init__.py b/lms/lms/doctype/lms_programming_exercise_submission/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_programming_exercise_submission/lms_programming_exercise_submission.js b/lms/lms/doctype/lms_programming_exercise_submission/lms_programming_exercise_submission.js new file mode 100644 index 00000000..43f0e871 --- /dev/null +++ b/lms/lms/doctype/lms_programming_exercise_submission/lms_programming_exercise_submission.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("LMS Programming Exercise Submission", { +// refresh(frm) { + +// }, +// }); diff --git a/lms/lms/doctype/lms_programming_exercise_submission/lms_programming_exercise_submission.json b/lms/lms/doctype/lms_programming_exercise_submission/lms_programming_exercise_submission.json new file mode 100644 index 00000000..759f4002 --- /dev/null +++ b/lms/lms/doctype/lms_programming_exercise_submission/lms_programming_exercise_submission.json @@ -0,0 +1,110 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-06-18 20:01:37.678342", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "exercise", + "status", + "column_break_jkjs", + "member", + "member_name", + "section_break_onmz", + "code", + "section_break_idyi", + "test_cases" + ], + "fields": [ + { + "fieldname": "exercise", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Exercise", + "options": "LMS Programming Exercise", + "reqd": 1 + }, + { + "fieldname": "member", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Member", + "options": "User", + "reqd": 1 + }, + { + "fetch_from": "member.full_name", + "fieldname": "member_name", + "fieldtype": "Data", + "label": "Member Name", + "read_only": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "\nPassed\nFailed" + }, + { + "fieldname": "column_break_jkjs", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_idyi", + "fieldtype": "Section Break" + }, + { + "fieldname": "test_cases", + "fieldtype": "Table", + "label": "Test Cases", + "options": "LMS Test Case Submission" + }, + { + "fieldname": "section_break_onmz", + "fieldtype": "Section Break" + }, + { + "fieldname": "code", + "fieldtype": "Code", + "label": "Code", + "reqd": 1 + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-06-19 12:14:36.604958", + "modified_by": "sayali@frappe.io", + "module": "LMS", + "name": "LMS Programming Exercise Submission", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "show_title_field_in_link": 1, + "sort_field": "creation", + "sort_order": "DESC", + "states": [ + { + "color": "Green", + "title": "Passed" + }, + { + "color": "Red", + "title": "Failed" + } + ], + "title_field": "member_name" +} diff --git a/lms/lms/doctype/lms_programming_exercise_submission/lms_programming_exercise_submission.py b/lms/lms/doctype/lms_programming_exercise_submission/lms_programming_exercise_submission.py new file mode 100644 index 00000000..ed8580fe --- /dev/null +++ b/lms/lms/doctype/lms_programming_exercise_submission/lms_programming_exercise_submission.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LMSProgrammingExerciseSubmission(Document): + pass diff --git a/lms/lms/doctype/lms_programming_exercise_submission/test_lms_programming_exercise_submission.py b/lms/lms/doctype/lms_programming_exercise_submission/test_lms_programming_exercise_submission.py new file mode 100644 index 00000000..1cefbd2c --- /dev/null +++ b/lms/lms/doctype/lms_programming_exercise_submission/test_lms_programming_exercise_submission.py @@ -0,0 +1,30 @@ +# Copyright (c) 2025, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase, UnitTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + +class UnitTestLMSProgrammingExerciseSubmission(UnitTestCase): + """ + Unit tests for LMSProgrammingExerciseSubmission. + Use this class for testing individual functions and methods. + """ + + pass + + +class IntegrationTestLMSProgrammingExerciseSubmission(IntegrationTestCase): + """ + Integration tests for LMSProgrammingExerciseSubmission. + Use this class for testing interactions between multiple components. + """ + + pass diff --git a/lms/lms/doctype/lms_test_case/__init__.py b/lms/lms/doctype/lms_test_case/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_test_case/lms_test_case.js b/lms/lms/doctype/lms_test_case/lms_test_case.js new file mode 100644 index 00000000..a2d9bece --- /dev/null +++ b/lms/lms/doctype/lms_test_case/lms_test_case.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("LMS Test Case", { +// refresh(frm) { + +// }, +// }); diff --git a/lms/lms/doctype/lms_test_case/lms_test_case.json b/lms/lms/doctype/lms_test_case/lms_test_case.json new file mode 100644 index 00000000..9886b3de --- /dev/null +++ b/lms/lms/doctype/lms_test_case/lms_test_case.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-06-18 16:12:10.010416", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "input", + "column_break_zkvg", + "expected_output" + ], + "fields": [ + { + "fieldname": "input", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Input" + }, + { + "fieldname": "column_break_zkvg", + "fieldtype": "Column Break" + }, + { + "fieldname": "expected_output", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Expected Output", + "reqd": 1 + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-06-18 16:37:03.538244", + "modified_by": "sayali@frappe.io", + "module": "LMS", + "name": "LMS Test Case", + "owner": "Administrator", + "permissions": [], + "row_format": "Dynamic", + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/lms/lms/doctype/lms_test_case/lms_test_case.py b/lms/lms/doctype/lms_test_case/lms_test_case.py new file mode 100644 index 00000000..4e9791ab --- /dev/null +++ b/lms/lms/doctype/lms_test_case/lms_test_case.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LMSTestCase(Document): + pass diff --git a/lms/lms/doctype/lms_test_case/test_lms_test_case.py b/lms/lms/doctype/lms_test_case/test_lms_test_case.py new file mode 100644 index 00000000..fc069157 --- /dev/null +++ b/lms/lms/doctype/lms_test_case/test_lms_test_case.py @@ -0,0 +1,30 @@ +# Copyright (c) 2025, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase, UnitTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + +class UnitTestLMSTestCase(UnitTestCase): + """ + Unit tests for LMSTestCase. + Use this class for testing individual functions and methods. + """ + + pass + + +class IntegrationTestLMSTestCase(IntegrationTestCase): + """ + Integration tests for LMSTestCase. + Use this class for testing interactions between multiple components. + """ + + pass diff --git a/lms/lms/doctype/lms_test_case_submission/__init__.py b/lms/lms/doctype/lms_test_case_submission/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_test_case_submission/lms_test_case_submission.json b/lms/lms/doctype/lms_test_case_submission/lms_test_case_submission.json new file mode 100644 index 00000000..00a22028 --- /dev/null +++ b/lms/lms/doctype/lms_test_case_submission/lms_test_case_submission.json @@ -0,0 +1,62 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-06-18 20:05:03.467705", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "input", + "expected_output", + "column_break_bsjs", + "output", + "status" + ], + "fields": [ + { + "fieldname": "input", + "fieldtype": "Data", + "label": "Input" + }, + { + "fieldname": "expected_output", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Expected Output", + "reqd": 1 + }, + { + "fieldname": "column_break_bsjs", + "fieldtype": "Column Break" + }, + { + "fieldname": "output", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Output", + "reqd": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Passed\nFailed", + "reqd": 1 + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-06-18 20:06:57.182153", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Test Case Submission", + "owner": "Administrator", + "permissions": [], + "row_format": "Dynamic", + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/lms/lms/doctype/lms_test_case_submission/lms_test_case_submission.py b/lms/lms/doctype/lms_test_case_submission/lms_test_case_submission.py new file mode 100644 index 00000000..8157ebd7 --- /dev/null +++ b/lms/lms/doctype/lms_test_case_submission/lms_test_case_submission.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LMSTestCaseSubmission(Document): + pass diff --git a/yarn.lock b/yarn.lock index a40b07c8..1e80e8e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -72,7 +72,17 @@ "@types/tough-cookie" "^4.0.5" tough-cookie "^4.1.4" -"@codemirror/commands@^6.3.0": +"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.3.2": + version "6.18.6" + resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz#de26e864a1ec8192a1b241eb86addbb612964ddb" + integrity sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg== + dependencies: + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.17.0" + "@lezer/common" "^1.0.0" + +"@codemirror/commands@6.x", "@codemirror/commands@^6.0.0", "@codemirror/commands@^6.3.0": version "6.8.1" resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.8.1.tgz#639f5559d2f33f2582a2429c58cb0c1b925c7a30" integrity sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw== @@ -82,6 +92,45 @@ "@codemirror/view" "^6.27.0" "@lezer/common" "^1.1.0" +"@codemirror/lang-css@^6.0.0": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-css/-/lang-css-6.3.1.tgz#763ca41aee81bb2431be55e3cfcc7cc8e91421a3" + integrity sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@lezer/common" "^1.0.2" + "@lezer/css" "^1.1.7" + +"@codemirror/lang-html@^6.4.9": + version "6.4.9" + resolved "https://registry.yarnpkg.com/@codemirror/lang-html/-/lang-html-6.4.9.tgz#d586f2cc9c341391ae07d1d7c545990dfa069727" + integrity sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/lang-css" "^6.0.0" + "@codemirror/lang-javascript" "^6.0.0" + "@codemirror/language" "^6.4.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.17.0" + "@lezer/common" "^1.0.0" + "@lezer/css" "^1.1.0" + "@lezer/html" "^1.3.0" + +"@codemirror/lang-javascript@^6.0.0", "@codemirror/lang-javascript@^6.2.4": + version "6.2.4" + resolved "https://registry.yarnpkg.com/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz#eef2227d1892aae762f3a0f212f72bec868a02c5" + integrity sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/language" "^6.6.0" + "@codemirror/lint" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.17.0" + "@lezer/common" "^1.0.0" + "@lezer/javascript" "^1.0.0" + "@codemirror/lang-json@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@codemirror/lang-json/-/lang-json-6.0.1.tgz#0a0be701a5619c4b0f8991f9b5e95fe33f462330" @@ -90,7 +139,18 @@ "@codemirror/language" "^6.0.0" "@lezer/json" "^1.0.0" -"@codemirror/language@^6.0.0", "@codemirror/language@^6.9.2": +"@codemirror/lang-python@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-python/-/lang-python-6.2.1.tgz#37c9930716110156865a95c548aa0eef5552863a" + integrity sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw== + dependencies: + "@codemirror/autocomplete" "^6.3.2" + "@codemirror/language" "^6.8.0" + "@codemirror/state" "^6.0.0" + "@lezer/common" "^1.2.1" + "@lezer/python" "^1.1.4" + +"@codemirror/language@6.x", "@codemirror/language@^6.0.0", "@codemirror/language@^6.4.0", "@codemirror/language@^6.6.0", "@codemirror/language@^6.8.0", "@codemirror/language@^6.9.2": version "6.11.1" resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.11.1.tgz#7e91a79cd05e278d5782ff9b4cafe8b83a699688" integrity sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ== @@ -102,7 +162,7 @@ "@lezer/lr" "^1.0.0" style-mod "^4.0.0" -"@codemirror/lint@^6.4.2": +"@codemirror/lint@^6.0.0", "@codemirror/lint@^6.4.2": version "6.8.5" resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.8.5.tgz#9edaa808e764e28e07665b015951934c8ec3a418" integrity sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA== @@ -111,7 +171,16 @@ "@codemirror/view" "^6.35.0" crelt "^1.0.5" -"@codemirror/state@^6.0.0", "@codemirror/state@^6.3.1", "@codemirror/state@^6.4.0", "@codemirror/state@^6.5.0": +"@codemirror/search@^6.0.0": + version "6.5.11" + resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.5.11.tgz#a324ffee36e032b7f67aa31c4fb9f3e6f9f3ed63" + integrity sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA== + dependencies: + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + crelt "^1.0.5" + +"@codemirror/state@6.x", "@codemirror/state@^6.0.0", "@codemirror/state@^6.3.1", "@codemirror/state@^6.4.0", "@codemirror/state@^6.5.0": version "6.5.2" resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.5.2.tgz#8eca3a64212a83367dc85475b7d78d5c9b7076c6" integrity sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA== @@ -128,7 +197,7 @@ "@codemirror/view" "^6.0.0" "@lezer/highlight" "^1.0.0" -"@codemirror/view@^6.0.0", "@codemirror/view@^6.22.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.27.0", "@codemirror/view@^6.35.0": +"@codemirror/view@6.x", "@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.22.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.27.0", "@codemirror/view@^6.35.0": version "6.37.2" resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.37.2.tgz#fe576641a2e809a50946567cd2b528c86f22b885" integrity sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw== @@ -591,18 +660,45 @@ resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.4.tgz#4d4ff677e1609214fc71c580125ddddd86abcabf" integrity sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w== -"@lezer/common@^1.0.0", "@lezer/common@^1.1.0", "@lezer/common@^1.2.0": +"@lezer/common@^1.0.0", "@lezer/common@^1.0.2", "@lezer/common@^1.1.0", "@lezer/common@^1.2.0", "@lezer/common@^1.2.1": version "1.2.3" resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.2.3.tgz#138fcddab157d83da557554851017c6c1e5667fd" integrity sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA== -"@lezer/highlight@^1.0.0": +"@lezer/css@^1.1.0", "@lezer/css@^1.1.7": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@lezer/css/-/css-1.2.1.tgz#b35f6d0459e9be4de1cdf4d3132a59efd7cf2ba3" + integrity sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg== + dependencies: + "@lezer/common" "^1.2.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.3.0" + +"@lezer/highlight@^1.0.0", "@lezer/highlight@^1.1.3": version "1.2.1" resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.2.1.tgz#596fa8f9aeb58a608be0a563e960c373cbf23f8b" integrity sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA== dependencies: "@lezer/common" "^1.0.0" +"@lezer/html@^1.3.0": + version "1.3.10" + resolved "https://registry.yarnpkg.com/@lezer/html/-/html-1.3.10.tgz#1be9a029a6fe835c823b20a98a449a630416b2af" + integrity sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w== + dependencies: + "@lezer/common" "^1.2.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/javascript@^1.0.0": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@lezer/javascript/-/javascript-1.5.1.tgz#2a424a6ec29f1d4ef3c34cbccc5447e373618ad8" + integrity sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw== + dependencies: + "@lezer/common" "^1.2.0" + "@lezer/highlight" "^1.1.3" + "@lezer/lr" "^1.3.0" + "@lezer/json@^1.0.0": version "1.0.3" resolved "https://registry.yarnpkg.com/@lezer/json/-/json-1.0.3.tgz#e773a012ad0088fbf07ce49cfba875cc9e5bc05f" @@ -612,13 +708,22 @@ "@lezer/highlight" "^1.0.0" "@lezer/lr" "^1.0.0" -"@lezer/lr@^1.0.0": +"@lezer/lr@^1.0.0", "@lezer/lr@^1.3.0": version "1.4.2" resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.2.tgz#931ea3dea8e9de84e90781001dae30dea9ff1727" integrity sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA== dependencies: "@lezer/common" "^1.0.0" +"@lezer/python@^1.1.4": + version "1.1.18" + resolved "https://registry.yarnpkg.com/@lezer/python/-/python-1.1.18.tgz#fa02fbf492741c82dc2dc98a0a042bd0d4d7f1d3" + integrity sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg== + dependencies: + "@lezer/common" "^1.2.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + "@marijn/find-cluster-break@^1.0.0": version "1.0.2" resolved "https://registry.yarnpkg.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz#775374306116d51c0c500b8c4face0f9a04752d8" @@ -1932,18 +2037,18 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -codemirror-editor-vue3@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/codemirror-editor-vue3/-/codemirror-editor-vue3-2.8.0.tgz#5d7e3c8bc1fac88e64f349fa03f8aac2e6b7a845" - integrity sha512-ebYGNhBpLmQNLguXzNyMMkn6K8v3lcS5/Ncvdn6YS4bLGEHE67MfsJIS/WV0L7I6WavUuFlY/Rs/AJKChIwSwg== +codemirror@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-6.0.1.tgz#62b91142d45904547ee3e0e0e4c1a79158035a29" + integrity sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg== dependencies: - codemirror "^5" - diff-match-patch "^1.0.5" - -codemirror@^5: - version "5.65.19" - resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.19.tgz#71016c701d6a4b6e1982b0f6e7186be65e49653d" - integrity sha512-+aFkvqhaAVr1gferNMuN8vkTSrWIFvzlMV9I2KBLCWS2WpZ2+UAkZjlMZmEuT+gcXTi6RrGQCkWq1/bDtGqhIA== + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/commands" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/lint" "^6.0.0" + "@codemirror/search" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" color-convert@^2.0.1: version "2.0.1" @@ -2265,11 +2370,6 @@ didyoumean@^1.2.2: resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== -diff-match-patch@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37" - integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw== - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -4940,6 +5040,11 @@ tailwindcss@^3.2.7: resolve "^1.22.8" sucrase "^3.35.0" +thememirror@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/thememirror/-/thememirror-2.0.1.tgz#ae9eb4ce7e8d0303d4fbabcc860ed38a0b45b079" + integrity sha512-d5i6FVvWWPkwrm4cHLI3t9AT1OrkAt7Ig8dtdYSofgF7C/eiyNuq6zQzSTusWTde3jpW9WLvA9J/fzNKMUsd0w== + thenify-all@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" @@ -5320,6 +5425,16 @@ vue-chartjs@^5.3.0: resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-5.3.2.tgz#c0f2009af6b08845af158ddee9d0a68d9dae631b" integrity sha512-NrkbRRoYshbXbWqJkTN6InoDVwVb90C0R7eAVgMWcB9dPikbruaOoTFjFYHE/+tNPdIe6qdLCDjfjPHQ0fw4jw== +vue-codemirror@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/vue-codemirror/-/vue-codemirror-6.1.1.tgz#246697ef4cfa6b2448dd592ade214bb7ff86611f" + integrity sha512-rTAYo44owd282yVxKtJtnOi7ERAcXTeviwoPXjIc6K/IQYUsoDkzPvw/JDFtSP6T7Cz/2g3EHaEyeyaQCKoDMg== + dependencies: + "@codemirror/commands" "6.x" + "@codemirror/language" "6.x" + "@codemirror/state" "6.x" + "@codemirror/view" "6.x" + vue-demi@>=0.13.0, vue-demi@>=0.14.8, vue-demi@^0.14.10: version "0.14.10" resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04"