diff --git a/icon.svg b/icon.svg
new file mode 100644
index 0000000..cb1265b
--- /dev/null
+++ b/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/index.css b/index.css
new file mode 100644
index 0000000..e69de29
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..ae959fe
--- /dev/null
+++ b/index.html
@@ -0,0 +1,67 @@
+
+
+
+ ').html(guidance)
+ guidanceContainer.append(guidanceText)
+ guidanceBlock.append(guidanceContainer)
+ }
+ }
+
+ const onExpandClick = () => {
+ const {EXPAND, COLLAPSE} = window.codioAssessmentsHelper.METHODS
+ const action = expanded ? COLLAPSE :EXPAND
+ expanded = !expanded
+ window.codioAssessmentsHelper.send(action)
+ }
+
+ const isString = (val) => typeof val === 'string' || val instanceof String
+
+ const renderFeedbackOutput = async (comment, format) => {
+ if (!isString(comment)) {
+ return null
+ }
+ if (format === window.codioAssessmentsHelper.SCRIPT_GRADE_FORMAT.MD) {
+ try {
+ const file = await unified()
+ .use(window.remarkParse)
+ .use(window.remarkRehype)
+ .use(window.rehypeExternalLinks, {rel: [], target: '_blank'})
+ .use(window.rehypeSanitize)
+ .use(window.rehypeStringify)
+ .process(comment)
+
+ return file.value
+ } catch (e) {
+ console.error(e)
+ }
+ } else if (format === window.codioAssessmentsHelper.SCRIPT_GRADE_FORMAT.HTML) {
+ return window.DOMPurify.sanitize(comment)
+ }
+ return comment.split('\n').map(item => {
+ return `
${window.codioAssessmentsHelper.escapeHTML(item)}
`
+ }).join('')
+ }
+
+ const getFeedbackContainer = () => {
+ const container = $('
')
+ container.attr('aria-label', `Scrollable feedback ${assessment.source.showName ? assessment.source.name : ''}`)
+ return container
+ }
+
+ const getValidHtml = (text) => {
+ return new DOMParser().parseFromString(text, 'text/html').querySelector("body").innerHTML
+ }
+
+ const cutOutput = (inputText) => {
+ return inputText.length < LONG_OUTPUT_LENGTH ? inputText : inputText.substring(0, LONG_OUTPUT_LENGTH) + '\n...'
+ }
+
+ const renderOutput = async (container) => {
+ const result = currentData?.result
+ if (result?.state === window.codioAssessmentsHelper.States.RESET) {
+ return
+ }
+ const outputEl = $('
')
+ if (isString(result?.feedback)) {
+ outputEl.removeClass('hide')
+ const feedbackContainer = getFeedbackContainer()
+ const feedback = await renderFeedbackOutput(result.feedback, result.format)
+ feedbackContainer.append(feedback)
+ outputEl.append(feedbackContainer)
+ if (result.code !== 0) {
+ const debugFeedbackContainer = getFeedbackContainer()
+ const debugFeedback = await renderFeedbackOutput(result.output)
+ debugFeedbackContainer.append(debugFeedback)
+ outputEl.append(debugFeedbackContainer)
+ }
+ return
+ }
+ if (isString(result?.output)) {
+ outputEl.removeClass('hide')
+ const outputContainer = getFeedbackContainer()
+ outputContainer.html(getValidHtml(cutOutput(result.output)))
+ outputEl.append(outputContainer)
+ }
+ container.append(outputEl)
+ }
+
+ const renderResult = () => {
+ const assessmentState = getAssessmentState()
+ const resultBlock = $('.codio-assessment-results-block')
+ resultBlock.empty()
+ if (!assessmentState.answered && !processing) {
+ return
+ }
+ const result = currentData?.result
+ const state = processing ? window.codioAssessmentsHelper.States.PROGRESS : result?.state
+ const resultEl = $(`
`)
+
+ const assessmentStatus = window.codioAssessmentsHelper.getAssessmentResultStatus(
+ assessment.source, result, processing
+ )
+ const iconStr = window.codioAssessmentsHelper.getIconByResultStatus(assessmentStatus)
+ const iconEl = $(iconStr).addClass(`codio-assessment-result-status-icon ${assessmentStatus}`)
+ const iconContainer = $('
')
+ iconContainer.append(iconEl)
+ resultEl.append(iconContainer)
+ const resultInfoContainer = $('
')
+
+ if (result?.timestamp) {
+ const timestamp = new Date(result.timestamp).toLocaleString()
+ const timestampEl = $(`
+
LAST RUN
+on ${timestamp}
+
+`)
+ resultInfoContainer.append(timestampEl)
+ }
+ renderOutput(resultInfoContainer)
+ resultEl.append(resultInfoContainer)
+
+ const resultActionsContainer = $('
')
+ const expandButton = $(`
+
+`)
+ expandButton.attr('aria-label', `Expand output ${assessment.source.showName ? assessment.source.name : ''}`)
+ expandButton.on('click', onExpandClick)
+ resultActionsContainer.append(expandButton)
+ resultEl.append(resultActionsContainer)
+ resultBlock.append(resultEl)
+ }
+
+ const getAssessmentState = () => {
+ const result = currentData ? currentData.result : null
+ const answered = result?.state && result?.state !== window.codioAssessmentsHelper.States.RESET
+ const usedAttempts = result?.usedAttempts || 0
+ const canAnswerAgain = !assessment.source.maxAttemptsCount || usedAttempts < assessment.source.maxAttemptsCount
+ const showModify = assessmentOptions.showUnblock && (!answered || canAnswerAgain)
+ const isDisabled = processing ||
+ assessmentOptions.isDisabled ||
+ result?.state === window.codioAssessmentsHelper.States.PROGRESS ||
+ answered && !canAnswerAgain
+ const teacherInStudentsProject = assessmentOptions.showAsTeacher && !assessmentOptions.owner
+
+ return {
+ isDisabled,
+ answered,
+ usedAttempts,
+ canAnswerAgain,
+ showModify,
+ teacherInStudentsProject
+ }
+ }
+
+ const refreshResultAndFooter = () => {
+ if (!assessment) {
+ return
+ }
+ renderResult()
+ updateFooterButtons()
+ }
+
+ const bindEvents = () => {
+ $('.check-button').on('click', onCheck)
+ $('.unblock-button').on('click', onUnblock)
+ $('.reset-button').on('click', onReset)
+
+ window.codioAssessmentsHelper.addBodyHeightListener()
+ }
+
+ const render = () => {
+ const container = $('.codio-assessment')
+ const nameEl = container.find('.codio-assessment-name')
+ assessment.source.showName ? nameEl.text(assessment.source.name) : nameEl.remove()
+ renderContent()
+ updateCheckButtonText()
+ renderGuidance()
+ refreshResultAndFooter()
+ bindEvents()
+ container.removeClass('hide')
+ }
+
+ const processMessage = (jsonData) => {
+ try {
+ const {method, data} = JSON.parse(jsonData)
+ console.log('assessment iframe processMessage', jsonData, method, data)
+ switch (method) {
+ case window.codioAssessmentsHelper.METHODS.GET_STYLES_RESPONSE:
+ window.codioAssessmentsHelper.addStyle(data.css)
+ break
+ case window.codioAssessmentsHelper.METHODS.GET_STATE_RESPONSE:
+ updateProcessing(false)
+ applyState(data)
+ break
+ case window.codioAssessmentsHelper.METHODS.CALLBACK: {
+ window.codioAssessmentsHelper.processCallback(data)
+ break
+ }
+ }
+ } catch {}
+ }
+
+ window.addEventListener('load', () => {
+ window.codioAssessmentsHelper.registerMessageListener(processMessage)
+ window.codioAssessmentsHelper.send(window.codioAssessmentsHelper.METHODS.GET_STATE)
+ window.codioAssessmentsHelper.send(window.codioAssessmentsHelper.METHODS.GET_STYLES)
+ })
+})()
diff --git a/java-grammar.js b/java-grammar.js
new file mode 100644
index 0000000..f2e69a5
--- /dev/null
+++ b/java-grammar.js
@@ -0,0 +1,231 @@
+window.codioTestAssessment = window.codioTestAssessment || {}
+window.codioTestAssessment.javaGrammar = window.codioTestAssessment.javaGrammar || {}
+
+window.codioTestAssessment.javaGrammar.SLASH = '/'
+window.codioTestAssessment.javaGrammar.BACK_SLASH = '\\'
+window.codioTestAssessment.javaGrammar.STAR = '*'
+window.codioTestAssessment.javaGrammar.DOUBLE_QUOTE = '"'
+window.codioTestAssessment.javaGrammar.NEW_LINE = '\n'
+window.codioTestAssessment.javaGrammar.LEFT_BRACE = '{'
+window.codioTestAssessment.javaGrammar.RIGHT_BRACE = '}'
+
+window.codioTestAssessment.javaGrammar.packageReg = new RegExp(
+ 'package\\s+([a-zA-Z_]{1}[a-zA-Z0-9_]*(\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*)*)\\s*;',
+ 'm'
+)
+window.codioTestAssessment.javaGrammar.classNameReg = new RegExp(
+ '\\s*(public|private)?(static)?\\s*class\\s+(\\w+)' +
+ '\\s*((extends\\s+\\w+)|(implements\\s+\\w+( ,\\w+)*))?\\s*\\{',
+ 'm'
+)
+window.codioTestAssessment.javaGrammar.anonClassNameReg = new RegExp('\\s*new\\s+(\\w+)\\s*\\(\\w*\\)\\s*\\{', 'm')
+
+function removeCommentsAndStrings(content) {
+ const {DOUBLE_QUOTE, BACK_SLASH, SLASH, STAR, NEW_LINE} = window.codioTestAssessment.javaGrammar
+ let updatedContent = ''
+ let isCommentStarted = false
+ let isStringStarted = false
+ let isMultiline = false
+ for (let i = 0, len = content.length; i < len; i++) {
+ let current = content[i]
+ if (current === DOUBLE_QUOTE && !isCommentStarted) {
+ if (content[i - 1] !== BACK_SLASH) {
+ isStringStarted = !isStringStarted
+ if (!isStringStarted) {
+ current = ' '
+ }
+ }
+ }
+ if (!isStringStarted && current === SLASH) {
+ if (content[i + 1] === SLASH) {
+ isCommentStarted = true
+ isMultiline = false
+ }
+ if (content[i + 1] === STAR) {
+ isCommentStarted = true
+ isMultiline = true
+ }
+ }
+ if (isCommentStarted) {
+ if (isMultiline) {
+ if (current === STAR && content[i + 1] === SLASH) {
+ isCommentStarted = false
+ current = ' '
+ i++
+ }
+ } else {
+ if (current === NEW_LINE) {
+ isCommentStarted = false
+ }
+ }
+ }
+ if (isStringStarted || isCommentStarted) {
+ if (current !== NEW_LINE) {
+ current = ' '
+ }
+ }
+ updatedContent += current
+ }
+ return updatedContent
+}
+
+const findClassEnd = (content) => {
+ const {RIGHT_BRACE, LEFT_BRACE} = window.codioTestAssessment.javaGrammar
+
+ let level = 0
+ for (let i = 0, len = content.length; i < len; i++) {
+ let current = content[i]
+ if (current === RIGHT_BRACE) {
+ if (level === 0) {
+ return i
+ } else {
+ level--
+ }
+ } else if (current === LEFT_BRACE) {
+ level++
+ }
+ }
+ return content.length
+}
+
+const classesInformation = (data) => {
+ const {classNameReg, anonClassNameReg} = window.codioTestAssessment.javaGrammar
+ let allClasses = []
+
+ let classSearch = data
+ let classNameMatch = classSearch.match(classNameReg)
+ let offset = 0
+ let start = 0
+ let end = 0
+ while (classNameMatch) {
+ start = classNameMatch.index + classNameMatch[0].length
+ classSearch = classSearch.substring(start, classSearch.length)
+ end = findClassEnd(classSearch)
+ allClasses.push({
+ name: classNameMatch[3],
+ start: start + offset,
+ end: start + end + offset
+ })
+ offset += start
+ classNameMatch = classSearch.match(classNameReg)
+ }
+
+ let anonClassSearch = data
+ let anonClassNameMatch = anonClassSearch.match(anonClassNameReg)
+ offset = 0
+ while (anonClassNameMatch) {
+ start = anonClassNameMatch.index + anonClassNameMatch[0].length
+ anonClassSearch = anonClassSearch.substring(start, anonClassSearch.length)
+ end = findClassEnd(anonClassSearch)
+ allClasses.push({
+ start: start + offset,
+ end: start + end + offset
+ })
+ offset += start
+ anonClassNameMatch = anonClassSearch.match(anonClassNameReg)
+ }
+
+ allClasses = allClasses.sort((a, b) => {
+ return a.start - b.start
+ })
+
+ const classesInfo = {}
+ const processed = []
+
+ allClasses.forEach(function (item, pos) {
+ if (pos === 0) {
+ classesInfo[item.name] = item
+ classesInfo[item.name].anon = 1
+ processed.push(classesInfo[item.name])
+ } else {
+ let lastNested = null
+ processed.forEach(function (parentItem) {
+ if (parentItem.start < item.start && parentItem.end > item.end) {
+ lastNested = parentItem
+ }
+ })
+ if (lastNested) {
+ let name
+ if (!item.name) {
+ name = lastNested.name + '$' + lastNested.anon
+ lastNested.anon++
+ } else {
+ name = lastNested.name + '$' + item.name
+ }
+
+ classesInfo[name] = item
+ classesInfo[name].anon = 1
+ classesInfo[name].name = name
+ processed.push(classesInfo[name])
+ }
+ }
+ })
+
+ return processed
+}
+
+const getClassInfo = (path, content) => {
+ const {packageReg} = window.codioTestAssessment.javaGrammar
+ const pathsPart = path.split('/')
+ const fileName = pathsPart[pathsPart.length - 1]
+
+ const information = {
+ packageName: '',
+ className: '',
+ fileName: fileName,
+ path: path
+ }
+
+ let advancedClassInformation
+ const withoutComments = removeCommentsAndStrings(content)
+
+ const packageMatch = withoutComments.match(packageReg)
+ if (packageMatch) {
+ information.packageName = packageMatch[1]
+ }
+ advancedClassInformation = classesInformation(withoutComments)
+
+ const lastNested = advancedClassInformation[0]
+
+ if (lastNested) {
+ information.className = lastNested.name
+ }
+
+ return information
+}
+
+window.codioTestAssessment.javaGrammar.getJavaInfo = (path, content) => {
+ const classInformation = getClassInfo(path, content)
+ if (!classInformation.className) {
+ const message = 'Java class not found in ' + path
+ throw new Error(message)
+ }
+ const hasTestAnnotations =
+ content.includes('@Test') ||
+ content.includes('@ParameterizedTest') ||
+ content.includes('@org.junit.jupiter.api.Test') ||
+ content.includes('@org.junit.jupiter.params.ParameterizedTest')
+ if (!hasTestAnnotations && !content.includes('extends')) {
+ const message = 'JUnit test not found in ' + path
+ throw new Error(message)
+ }
+ const result = {}
+ const jUnit5Case = content.includes('.jupiter.')
+ if (jUnit5Case) {
+ const wrongNaming = !(
+ classInformation.className.endsWith('Test') ||
+ classInformation.className.endsWith('Tests')
+ )
+ if (wrongNaming) {
+ result.info = `${classInformation.className} class name do not match against class names ending in Test or Tests. This test may not function correctly.`
+ }
+ }
+ const className = !classInformation.packageName
+ ? classInformation.className
+ : classInformation.packageName + '.' + classInformation.className
+ return {
+ ...result,
+ filePath: path,
+ className
+ }
+}
diff --git a/metadata.json b/metadata.json
new file mode 100644
index 0000000..e9caa94
--- /dev/null
+++ b/metadata.json
@@ -0,0 +1,16 @@
+{
+ "name": "Advanced Code Test",
+ "type": "assessment",
+ "properties": {
+ "type": "test",
+ "icon": "./icon.svg",
+ "defaultHeight": 500,
+ "gradingControls": {
+ "points": true,
+ "allowPartialPoints": true,
+ "useMaximumScore": true,
+ "definedNumberOfAttempts": true,
+ "rationale": true
+ }
+ }
+}
diff --git a/settings.css b/settings.css
new file mode 100644
index 0000000..d6876a1
--- /dev/null
+++ b/settings.css
@@ -0,0 +1,60 @@
+.settings-content-row-container {
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+}
+
+.test-case-controls-container {
+ align-items: end;
+}
+
+.test-case-add-case-container {
+ flex: 1;
+}
+
+.test-case-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.test-case-item {
+ display: flex;
+ border: 1px solid rgba(0, 0, 0, 0.6);
+ padding: 10px;
+ gap: 10px;
+ border-radius: 6px;
+ align-items: center;
+}
+
+.test-case-item-info {
+ flex: 1;
+}
+
+.test-case-info-row {
+ display: flex;
+ gap: 6px;
+}
+
+.test-case-info-label {
+ font-weight: 600;
+}
+
+.test-case-item-actions {
+ display: flex;
+}
+
+.test-case-delete-button {
+ background: none;
+ border: none;
+ padding: 0;
+ width: 24px;
+ height: 24px;
+ cursor: pointer;
+ opacity: 0.8;
+ color: red;
+
+ &:hover {
+ opacity: 1;
+ }
+}
diff --git a/settings.html b/settings.html
new file mode 100644
index 0000000..872d5a3
--- /dev/null
+++ b/settings.html
@@ -0,0 +1,197 @@
+
+
+
+
+
Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Script will be executed isolated from user container
click here
+
+
+
+
+
+
+
+
+
Test cases
+
+ No test cases added
+
+
+
+
+
+
diff --git a/settings.js b/settings.js
new file mode 100644
index 0000000..8da93cb
--- /dev/null
+++ b/settings.js
@@ -0,0 +1,296 @@
+(function () {
+ const ICON_DELETE = '
'
+ let instructionsEditor = null
+
+ const LANG_TYPES = {
+ JAVA: 'java',
+ CUSTOM: 'custom',
+ RUBY: 'ruby',
+ PYTHON: 'python',
+ JAVASCRIPT: 'javascript'
+ }
+
+ const SUBTYPES_BY_LANG = {
+ JAVA: {
+ STYLE: 'style',
+ JUNIT: 'junit'
+ },
+ RUBY: {
+ STYLE: 'style',
+ RSPEC: 'rspec'
+ },
+ PYTHON: {
+ STYLE: 'style',
+ UNITTEST: 'unittest'
+ },
+ JAVASCRIPT: {
+ JSHINT: 'jshint',
+ JSLINT: 'jslint'
+ }
+ }
+
+ const EXTENSIONS_BY_TYPE = {
+ [LANG_TYPES.JAVA]: ['java'],
+ [LANG_TYPES.RUBY]: ['rb'],
+ [LANG_TYPES.PYTHON]: ['py'],
+ [LANG_TYPES.JAVASCRIPT]: ['js']
+ }
+
+ const TEST_TIMEOUT = 300
+
+ const getCodeEnvConfig = () => {
+ const langType = $('#languageType').val()
+
+ if (langType === LANG_TYPES.CUSTOM) {
+ return null
+ }
+
+ const subtype = $(`#${langType}LangSubtype`).val()
+ const isJava = langType === LANG_TYPES.JAVA
+
+ const files = []
+ $(`.test-case-item`).each(function() {
+ const test = $(this)
+ const filePath = test.find('.test-case-info-path').find('.test-case-info-text').text()
+ if (isJava && subtype === SUBTYPES_BY_LANG.JAVA.JUNIT) {
+ const className = test.find('.test-case-info-class').find('.test-case-info-text').text()
+ files.push({filePath, className})
+ } else {
+ files.push(filePath)
+ }
+ })
+
+ let envConfig = {
+ type: langType,
+ subtype,
+ files
+ }
+ if (isJava) {
+ if (subtype === SUBTYPES_BY_LANG.JAVA.JUNIT) {
+ const javaConfig = {
+ wd: $('#javaJunitWorkingDirectory').val(),
+ sources: $('#javaJunitSourcePath').val(),
+ libs: $('#javaJunitLibraryPath').val(),
+ testsources: $('#junitTestsSourcePath').val(),
+ jUnitVersion: $('#javaJunitVersion').val()
+ }
+ envConfig = {...envConfig, ...javaConfig}
+ } else if (subtype === SUBTYPES_BY_LANG.JAVA.STYLE) {
+ envConfig = {
+ ...envConfig,
+ configFile: $('#javaStyleConfigPath').val(),
+ checkStyleVersion: $('#javaCheckStyleVersion').val()
+ }
+ }
+ } else if (langType === LANG_TYPES.PYTHON && subtype === SUBTYPES_BY_LANG.PYTHON.UNITTEST) {
+ envConfig = {
+ ...envConfig,
+ executable: $('#pythonUnittestExecutable').val(),
+ pythonwd: $('#pythonUnittestWorkingDirectory').val()
+ }
+ }
+ return envConfig
+ }
+
+ const collectSettings = () => {
+ const errors = []
+ const instructions = instructionsEditor.getContent()
+ const timeout = parseInt($('#timeout').val(), 10);
+ const langType = $('#languageType').val()
+ const subtype = $(`#${langType}LangSubtype`).val()
+
+ const data = {
+ instructions,
+ timeout
+ }
+
+ const pythonUnittestStudentFolder = $('#pythonUnittestStudentFolder').val()
+ if (langType === LANG_TYPES.PYTHON && subtype === SUBTYPES_BY_LANG.PYTHON.UNITTEST && pythonUnittestStudentFolder) {
+ data.pythonPath = pythonUnittestStudentFolder
+ }
+ if (langType === LANG_TYPES.CUSTOM) {
+ data.command = $('#customCommand').val()
+ !data.command && errors.push('Command field must be completed')
+ } else {
+ const config = getCodeEnvConfig() || {}
+ data.codeEnvConfig = JSON.stringify(config)
+ data.command = 'python /usr/share/codio/assessments/assessments.py'
+ data.timeout = TEST_TIMEOUT
+ if (!config.files?.length) {
+ errors.push('Add a test case')
+ }
+ }
+ return {data, errors};
+ }
+
+ const exportSettings = () => {
+ const data = collectSettings();
+ window.codioAssessmentsHelper.send(window.codioAssessmentsHelper.METHODS.EXPORT_SETTINGS_RESPONSE, data);
+ }
+
+ const applySettings = (settings = {}) => {
+ instructionsEditor.setContent(settings.instructions || '')
+ $('#timeout').val(settings.timeout || '40');
+ const config = settings.codeEnvConfig ? JSON.parse(settings.codeEnvConfig) : null
+ onLanguageChanged(config?.type || LANG_TYPES.CUSTOM)
+ }
+
+ const processMessage = (jsonData) => {
+ console.log('settings iframe processMessage', jsonData)
+ try {
+ const {method, data} = JSON.parse(jsonData);
+ switch (method) {
+ case window.codioAssessmentsHelper.METHODS.EXPORT_SETTINGS:
+ exportSettings();
+ break;
+ case window.codioAssessmentsHelper.METHODS.GET_SETTINGS_RESPONSE:
+ applySettings(data.settings);
+ break;
+ case window.codioAssessmentsHelper.METHODS.CALLBACK: {
+ window.codioAssessmentsHelper.processCallback(data)
+ break
+ }
+ }
+ } catch {}
+ }
+
+ const onSubtypeChanged = (language, subtype) => {
+ $('.lang-subtype-settings-container').addClass('hide')
+ $(`.lang-subtype-${language}-${subtype}-settings`).removeClass('hide')
+ }
+
+ const onLanguageChanged = (language) => {
+ $('.language-settings-container').addClass('hide')
+ const testsContainer = $('.test-case-container')
+ testsContainer.addClass('hide')
+ testsContainer.find('.test-case-list').empty()
+ if (language !== LANG_TYPES.CUSTOM) {
+ testsContainer.removeClass('hide')
+ }
+ $(`.${language}-container`).removeClass('hide')
+ const subtype = $(`#${language}LangSubtype`).val()
+ onSubtypeChanged(language, subtype)
+ }
+
+ const addParsedTestCase = (info) => {
+ const path = info?.filePath || info
+ const className = info?.className
+
+ const list = $('.test-case-list')
+ const itemContainer = $('
')
+ itemContainer.data('path', path)
+ const infoContainer = $('
')
+ const pathInfoRow = $('
')
+ pathInfoRow.append('
Path:
')
+ pathInfoRow.append(`
${path}
`)
+ infoContainer.append(pathInfoRow)
+ if (className) {
+ const classInfoRow = $('
')
+ classInfoRow.append('
Class name:
')
+ classInfoRow.append(`
${className}
`)
+ infoContainer.append(classInfoRow)
+ }
+
+ const actionsContainer = $('
')
+ const deleteBtn = $(`
`)
+ actionsContainer.append(deleteBtn)
+ itemContainer.append(infoContainer)
+ itemContainer.append(actionsContainer)
+ list.append(itemContainer)
+ updateTestsHelpBlockVisibility()
+ }
+
+ const updateTestsHelpBlockVisibility = () => {
+ const list = $('.test-case-list')
+ const hasItems = !!list.find('.test-case-item')[0]
+ const helpBlock = $('.test-case-noItems-block')
+ hasItems ? helpBlock.addClass('hide') : helpBlock.removeClass('hide')
+ }
+
+ const removeTestCase = (item) => {
+ // todo confirm removal
+ item.remove()
+ updateTestsHelpBlockVisibility()
+ }
+
+ const addTestCase = async (path) => {
+ console.log('add test case', path)
+ if ($(`.test-case-item[data-path="${path}"]`)[0]) {
+ // todo already added warning
+ return
+ }
+ try {
+ // todo need to implement get/set content for settings
+ const {content} = await window.codioAssessmentsHelper.sendAndWait(
+ window.codioAssessmentsHelper.METHODS.GET_FILE_CONTENT, {path}
+ )
+ const langType = $('#languageType').val()
+ if (!EXTENSIONS_BY_TYPE[langType]) {
+ return
+ }
+ const subtype = $(`#${langType}LangSubtype`).val()
+ const ext = path.split('.').pop()
+ if (langType === LANG_TYPES.JAVA && subtype === 'style' && ext === 'xml') {
+ $('#javaStyleConfigPath').val(path)
+ return
+ }
+ if (!EXTENSIONS_BY_TYPE[langType].includes(ext)) {
+ console.error(`Ext ${ext} not supported`)
+ // todo error 'Incorrect file type, should be: ' + typeToExtension[type].join(' ')
+ return
+ }
+ if (langType === LANG_TYPES.JAVA && subtype === SUBTYPES_BY_LANG.JAVA.STYLE) {
+ try {
+ const info = window.codioTestAssessment.javaGrammar.getJavaInfo(path, content)
+ addParsedTestCase(info)
+ } catch (e) {
+ // todo show errors
+ }
+ } else {
+ addParsedTestCase(path)
+ }
+ } catch (e) {
+ // todo show error
+ }
+ }
+
+ const bindEvents = () => {
+ $('#languageType').on('change', function () {
+ const languageType = $(this).val();
+ onLanguageChanged(languageType);
+ })
+ $('.lang-subtype-select').on('change', function () {
+ const langSubtype = $(this).val();
+ onSubtypeChanged($('#languageType').val(), langSubtype);
+ })
+ $('#customCommand').on('input', function () {
+ const helpBlock = $('.secure-folder-help-block')
+ helpBlock.addClass('hide')
+ if ($(this).val().includes('.guides/secure')) {
+ helpBlock.removeClass('hide')
+ }
+ })
+ $('#newCasePath').on('input', function () {
+ const addCaseBtn = $('.add-case-btn')
+ addCaseBtn.prop('disabled', !$(this).val())
+ })
+ $('.add-case-btn').on('click', function () {
+ addTestCase($('#newCasePath').val())
+ })
+ $('.test-case-list').on('click', '.test-case-delete-button', function () {
+ removeTestCase($(this).closest('.test-case-item'));
+ })
+ }
+
+ const onLoad = async () => {
+ window.codioAssessmentsHelper.registerMessageListener(processMessage)
+ window.codioAssessmentsHelper.send(window.codioAssessmentsHelper.METHODS.GET_SETTINGS)
+
+ bindEvents()
+ instructionsEditor = window.codioAssessmentsHelper.initializeMarkdownEditor('instructions', 'instructions-command-bar')
+ }
+
+ window.addEventListener('load', onLoad);
+})()