├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .travis.yml ├── AUTHORS ├── CONTRIBUTING.md ├── DevDoc.md ├── LICENSE ├── README.md ├── assets ├── base.css ├── images │ ├── congrats_cake.gif │ ├── fail.png │ └── pass.png ├── questions │ ├── bomberman.js │ ├── checkBalancedParentheses.js │ ├── findAlphabet.js │ ├── findBestMeetupLocation.js │ ├── findClosestValueBst.js │ ├── findFirstNonRepeatingCharacter.js │ ├── findMostCommonCharacter.js │ ├── findMostCommonRepeatedCharacter.js │ ├── getStrobogrammaticNumbers.js │ ├── incrementDecimalCodedNumber.js │ ├── internationalization.js │ ├── isLeapYear.js │ ├── isPalindrome.js │ ├── longestSubstring.js │ ├── pirateTranslator.js │ ├── reverseWords.js │ ├── runLengthEncoding.js │ ├── sortItinerary.js │ └── splitStringIntoWords.js └── tests │ ├── CodeCheckerService.js │ ├── QuestionSchemaValidationService.js │ ├── QuestionSchemaValidationServiceSpec.js │ ├── TaskSchemaValidationService.js │ └── TaskSchemaValidationServiceSpec.js ├── client ├── config │ ├── config.js │ └── deploymentSpecificConfig.js ├── data │ ├── data.js │ ├── domain │ │ ├── BuggyOutputTestObjectFactory.js │ │ ├── PerformanceTestObjectFactory.js │ │ ├── QuestionObjectFactory.js │ │ ├── QuestionObjectFactorySpec.js │ │ ├── SuiteLevelTestObjectFactory.js │ │ ├── SuiteLevelTestObjectFactorySpec.js │ │ ├── TaskObjectFactory.js │ │ ├── TaskObjectFactorySpec.js │ │ ├── TestCaseObjectFactory.js │ │ ├── TestCaseObjectFactorySpec.js │ │ ├── TestSuiteObjectFactory.js │ │ ├── TestSuiteObjectFactorySpec.js │ │ ├── TipObjectFactory.js │ │ └── TipObjectFactorySpec.js │ └── services │ │ ├── QuestionDataService.js │ │ └── QuestionDataServiceSpec.js ├── docs │ ├── py-primer-dark.html │ └── py-primer-light.html ├── menu.html ├── menu │ ├── components │ │ ├── MenuQuestionCardDirective.js │ │ └── MenuViewDirective.js │ └── menu.js ├── question.html └── question │ ├── components │ ├── CodeSnippetDirective.js │ ├── ErrorSnippetDirective.js │ ├── HtmlWithMarkdownLinksSnippetDirective.js │ ├── HtmlWithMarkdownLinksSnippetDirectiveSpec.js │ ├── LearnerViewDirective.js │ ├── LearnerViewDirectiveSpec.js │ ├── MonospaceDisplayModalDirective.js │ ├── OutputSnippetDirective.js │ ├── SpeechBalloonDirectives.js │ └── SpeechBalloonsContainerDirective.js │ ├── domain │ ├── CodeEvalResultObjectFactory.js │ ├── CodeEvalResultObjectFactorySpec.js │ ├── CodeSubmissionObjectFactory.js │ ├── CodeSubmissionObjectFactorySpec.js │ ├── ErrorTracebackObjectFactory.js │ ├── ErrorTracebackObjectFactorySpec.js │ ├── FeedbackDetailsObjectFactory.js │ ├── FeedbackDetailsObjectFactorySpec.js │ ├── FeedbackObjectFactory.js │ ├── FeedbackObjectFactorySpec.js │ ├── FeedbackParagraphObjectFactory.js │ ├── FeedbackParagraphObjectFactorySpec.js │ ├── LearnerViewSubmissionResultObjectFactory.js │ ├── LearnerViewSubmissionResultObjectFactorySpec.js │ ├── PreprocessedCodeObjectFactory.js │ ├── PreprocessedCodeObjectFactorySpec.js │ ├── PrereqCheckErrorObjectFactory.js │ ├── PrereqCheckErrorObjectFactorySpec.js │ ├── PrereqCheckFailureObjectFactory.js │ ├── SnapshotObjectFactory.js │ ├── SnapshotObjectFactorySpec.js │ ├── SpeechBalloonObjectFactory.js │ ├── SpeechBalloonObjectFactorySpec.js │ ├── TracebackCoordinatesObjectFactory.js │ ├── TranscriptObjectFactory.js │ └── TranscriptObjectFactorySpec.js │ ├── question.js │ ├── questionSpec.js │ └── services │ ├── AutosaveService.js │ ├── AutosaveServiceSpec.js │ ├── ConversationManagerService.js │ ├── ConversationManagerServiceSpec.js │ ├── ConversationManagerServiceTestUtils.js │ ├── CurrentQuestionService.js │ ├── CurrentQuestionServiceSpec.js │ ├── EventHandlerService.js │ ├── EventHandlerServiceSpec.js │ ├── FeedbackGeneratorService.js │ ├── FeedbackGeneratorServiceSpec.js │ ├── LearnerStateService.js │ ├── LearnerStateServiceSpec.js │ ├── LocalStorageKeyManagerService.js │ ├── LocalStorageKeyManagerServiceSpec.js │ ├── LocalStorageService.js │ ├── LocalStorageServiceSpec.js │ ├── MonospaceDisplayModalService.js │ ├── MonospaceDisplayModalServiceSpec.js │ ├── ParentPageService.js │ ├── ParentPageServiceSpec.js │ ├── PrintTerminalService.js │ ├── PrintTerminalServiceSpec.js │ ├── ServerHandlerService.js │ ├── ServerHandlerServiceSpec.js │ ├── SessionHistoryService.js │ ├── SessionHistoryServiceSpec.js │ ├── SessionIdService.js │ ├── SessionIdServiceSpec.js │ ├── ThemeNameService.js │ ├── ThemeNameServiceSpec.js │ ├── UnpromptedFeedbackManagerService.js │ ├── UnpromptedFeedbackManagerServiceSpec.js │ ├── code_evaluators │ ├── CodeRunnerDispatcherService.js │ ├── CodeRunnerDispatcherServiceSpec.js │ ├── PrereqCheckDispatcherService.js │ ├── PrereqCheckDispatcherServiceSpec.js │ ├── PythonCodeRunnerService.js │ ├── PythonCodeRunnerServiceSpec.js │ ├── PythonPrereqCheckService.js │ └── PythonPrereqCheckServiceSpec.js │ └── code_preprocessors │ ├── CodePreprocessorDispatcherService.js │ ├── CodePreprocessorDispatcherServiceSpec.js │ ├── PythonCodePreprocessorService.js │ └── PythonCodePreprocessorServiceSpec.js ├── hooks └── pre-push ├── karma.conf.js ├── scripts ├── run_e2e_tests.sh ├── run_karma_tests.sh ├── run_linter.sh └── setup.sh ├── tests └── e2e │ ├── protractor.conf.js │ ├── question.pageObject.js │ ├── question.spec.js │ └── utils.js └── third_party ├── angular-1.6.1 ├── angular-aria.min.js ├── angular-mocks.js ├── angular-sanitize.min.js ├── angular.min.js └── angular.min.js.map ├── angular-cookies-1.6.1 ├── angular-cookies.min.js └── angular-cookies.min.js.map ├── codemirror-5.19.0 ├── addon │ └── edit │ │ └── matchbrackets.js ├── lib │ ├── codemirror.css │ ├── codemirror.js │ └── mbo.css └── mode │ └── python │ └── python.js ├── skulpt-12272b ├── skulpt-stdlib.js └── skulpt.min.js └── ui-codemirror-0.3.0 └── ui-codemirror.min.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Source files 2 | # ============ 3 | *.css text eol=lf 4 | *.html text eol=lf 5 | *.md text eol=lf 6 | *.map text eol=lf 7 | *.js text eol=lf 8 | *.json text eol=lf 9 | 10 | 11 | 12 | # Misc files 13 | # ========== 14 | .gitattributes text eol=lf 15 | .gitignore text eol=lf 16 | AUTHORS text eol=lf 17 | CHANGELOG text eol=lf 18 | CONTRIBUTORS text eol=lf 19 | LICENSE text eol=lf 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | tools/* 3 | .DS_Store 4 | assets/.DS_Store 5 | karma_coverage_reports/* 6 | *~ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | addons: 2 | apt: 3 | packages: 4 | - google-chrome-stable 5 | sources: 6 | - google-chrome 7 | cache: 8 | directories: 9 | - ./node_modules/ 10 | - ./third_party/ 11 | - ./tools/ 12 | dist: trusty 13 | install: true 14 | language: python 15 | python: 16 | - 2.7 17 | sudo: required 18 | 19 | branches: 20 | only: 21 | - master 22 | before_install: 23 | - pip install --upgrade pip 24 | - pip install codecov 25 | - export CHROME_BIN=chromium-browser 26 | - export DISPLAY=:99.0 27 | - sh -e /etc/init.d/xvfb start 28 | script: 29 | - bash scripts/run_karma_tests.sh --single-run --enable-coverage 30 | - bash scripts/run_linter.sh 31 | - bash scripts/run_e2e_tests.sh 32 | after_success: 33 | - codecov --file ./karma_coverage_reports/coverage-final.json 34 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of TIE authors for copyright purposes. 2 | # 3 | # This does not necessarily list everyone who has contributed code, since in 4 | # some cases, their employer may be the copyright holder. To see the full list 5 | # of contributors, see the revision history in source control. 6 | Google Inc. 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | TIE is currently in a very early stage of development, and we do not yet have a 4 | process for accepting external contributions. We'll update this file when 5 | this changes. 6 | 7 | In the future, when we are ready to accept external contributions, there are a 8 | few small guidelines that you'll need to follow. 9 | 10 | ## Contributor License Agreement 11 | 12 | Contributions to this project must be accompanied by a Contributor License 13 | Agreement. You (or your employer) retain the copyright to your contribution, 14 | this simply gives us permission to use and redistribute your contributions as 15 | part of the project. Head over to to see 16 | your current agreements on file or to sign a new one. 17 | 18 | You generally only need to submit a CLA once, so if you've already submitted 19 | one (even if it was for a different project), you probably don't need to do it 20 | again. 21 | 22 | ## Code reviews 23 | 24 | All submissions, including submissions by project members, require review. We 25 | use GitHub pull requests for this purpose. Consult [GitHub Help] for more 26 | information on using pull requests. 27 | 28 | [GitHub Help]: https://help.github.com/articles/about-pull-requests/ 29 | -------------------------------------------------------------------------------- /DevDoc.md: -------------------------------------------------------------------------------- 1 | # Development Manual 2 | 3 | This document is meant to help serve as a guide for developers to TIE. 4 | 5 | ## File Organization 6 | 7 | Right now, TIE's code is broken up into a series of folders: 8 | 9 | * `assets`: This folder holds the information about our questions. 10 | 11 | * `client`: This folder holds the majority of our code - including all of the components, code processors, evaluators, etc. 12 | 13 | * `hooks`: This folder currently just houses the `pre-push` script. 14 | 15 | * `karma_coverage_reports`: This folder is where the coverage reports are stored and updated. 16 | 17 | * `protractor_tests`: This folder houses our End-to-End (e2e) tests and configurations. 18 | 19 | * `scripts`: This folder has all of our scripts - mainly used for testing purposes. 20 | 21 | * `third_party`: This folder houses all of our approved third party code. 22 | 23 | ## Testing 24 | 25 | All of our unit tests are created in our `*Spec.js`-named files. The `*` represents the file name 26 | for the component(s) that the sister `Spec` file is testing. 27 | 28 | It is highly encouraged that you write tests for as much code coverage as possible. In order to test 29 | for coverage, you can run: 30 | 31 | `bash scripts/run_karma_tests.sh --enable-coverage` 32 | 33 | to run Karma tests in addition to generating coverage reports in the `karma_coverage_reports` 34 | folder. In order to see the report itself, simply open `karma_coverage_reports/index.html` in your 35 | browser. There, you'll be able to see the lines that have been hit or missed from your tests. 36 | 37 | ## Style 38 | 39 | We want to make sure that our code keeps a fairly consistent coding style, and in order to make it easy for you, we have a linter that you can run to double check that your code is aligned with our guidelines. 40 | To run the linter, simply run: 41 | 42 | `bash scripts/run_linter.sh` 43 | 44 | ## Dev workflow 45 | 46 | All code that gets merged with our `master` branch must be reviewed and approved. 47 | Thus, for each change you want to add to the repo, make sure to create a separate branch. Then, once 48 | you're satisfied with the changes, you can push up your code and submit a Pull Request (PR). 49 | In the PR description, simply and concisely state what you've changed in that branch. 50 | Once you've submitted a PR, at least one other developer will have to review and approve your code 51 | before it can then be finally merged. If your reviewer requests any changes, you must address them 52 | before you can get a "LGTM" (Looks good to me!). 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Technical Interview Exercises [![Build Status](https://api.travis-ci.org/google/tie.svg?branch=master)](https://travis-ci.org/google/tie) [![Code Coverage](https://codecov.io/github/google/tie/coverage.svg?branch=master)](https://codecov.io/github/google/tie/?branch=master) 2 | 3 | Technical Interview Exercises (TIE) is a lightweight open-source tool aimed at 4 | helping students learn key concepts, principles, and coding patterns that are 5 | important in software engineering. TIE allows users to write code to solve 6 | technical coding challenges and receive real time insightful and guiding 7 | feedback. 8 | 9 | The project is currently in early Beta. This means that content and features are 10 | limited. We plan to add more content and features as we develop towards version 11 | 1. 12 | 13 | ## User setup 14 | 15 | 1. From https://github.com/google/tie/, click on the green "Clone or download" 16 | button then click "Download ZIP" 17 | 2. Unzip the zip file 18 | 3. Open the file client/question.html in a web browser 19 | 4. Use the coding window on the right to code a solution and click "I think I'm 20 | done" if you think you have answer (repeat until you solve the question) 21 | 5. To stop the TIE application, simply close the browser window 22 | 6. To remove TIE from your computer, delete the downloaded files from steps 1 23 | and 2 24 | 25 | ## Disclaimer 26 | 27 | This is not an official Google product. TIE is provided "as is" and without 28 | warranty (refer to the TIE LICENSE file for details). 29 | -------------------------------------------------------------------------------- /assets/base.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 The TIE Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS-IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | html { 18 | height: 100%; 19 | min-height: 675px; 20 | min-width: 675px; 21 | } 22 | body { 23 | background-color: rgb(242, 242, 242); 24 | font-family: Roboto, 'Helvetica Neue', 'Lucida Grande', sans-serif; 25 | font-size: 15px; 26 | height: 100%; 27 | margin: 0px; 28 | } 29 | -------------------------------------------------------------------------------- /assets/images/congrats_cake.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/tie/156c8410de2e4a85f2ccba5e1bb07e80b7598bf6/assets/images/congrats_cake.gif -------------------------------------------------------------------------------- /assets/images/fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/tie/156c8410de2e4a85f2ccba5e1bb07e80b7598bf6/assets/images/fail.png -------------------------------------------------------------------------------- /assets/images/pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/tie/156c8410de2e4a85f2ccba5e1bb07e80b7598bf6/assets/images/pass.png -------------------------------------------------------------------------------- /assets/questions/getStrobogrammaticNumbers.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Question data for strobogrammatic number. 17 | */ 18 | 19 | globalData.questions['getStrobogrammaticNumbers'] = { // eslint-disable-line dot-notation 20 | title: 'Get N-Digit Strobogrammatic Numbers', 21 | starterCode: { 22 | python: 23 | `def getStrobogrammaticNumbers(num_digits): 24 | return [] 25 | ` 26 | }, 27 | auxiliaryCode: { 28 | python: 29 | `class AuxiliaryCode(object): 30 | @classmethod 31 | def allowLeadingZeroInResult(cls, num_digits): 32 | if num_digits == 2: 33 | return [["00", "11", "69", "88", "96"]] 34 | if num_digits == 3: 35 | return [[ 36 | '000', '010', '080', '101', '111', '181', '609', '619', '689', 37 | '808', '818', '906', '916', '986']] 38 | 39 | @classmethod 40 | def forgetToIncludeZero(cls, num_digits): 41 | if num_digits == 1: 42 | return [["1", "8"]] 43 | ` 44 | }, 45 | tasks: [{ 46 | id: 'generateStrobogrammaticNumbers', 47 | instructions: [{ 48 | content: [ 49 | 'Implement a function getStrobogrammaticNumbers which takes a ', 50 | 'number n as input and generates a list of n-digit numbers which ', 51 | 'look the same if you rotate them 180 degrees.' 52 | ].join(''), 53 | type: 'text' 54 | }, { 55 | content: 'Input: 2\nOutput: ["11", "69", "88", "96"]', 56 | type: 'code' 57 | }], 58 | prerequisiteSkills: ['Arrays'], 59 | acquiredSkills: ['Arrays'], 60 | inputFunctionName: null, 61 | outputFunctionName: null, 62 | mainFunctionName: 'getStrobogrammaticNumbers', 63 | languageSpecificTips: { 64 | python: [] 65 | }, 66 | testSuites: [{ 67 | id: 'SMALL_NUMBERS', 68 | humanReadableName: 'small-sized numbers', 69 | testCases: [{ 70 | input: '2', 71 | allowedOutputs: [["11", "69", "88", "96"]] 72 | }, { 73 | input: '1', 74 | allowedOutputs: [['0', '1', '8']] 75 | }] 76 | }, { 77 | id: 'THREE_DIGIT_NUMBERS', 78 | humanReadableName: '3-digit numbers', 79 | testCases: [{ 80 | input: '3', 81 | allowedOutputs: [ 82 | [ 83 | '101', '111', '181', '609', '619', '689', '808', '818', '906', 84 | '916', '986' 85 | ] 86 | ] 87 | }] 88 | }], 89 | buggyOutputTests: [{ 90 | buggyFunctionName: 'AuxiliaryCode.forgetToIncludeZero', 91 | ignoredTestSuiteIds: [], 92 | messages: [ 93 | "Which 1-digit numbers does your function return?", 94 | [ 95 | 'Are you returning all appropriate numbers?', 96 | '0, for instance, should be included.' 97 | ].join('') 98 | ] 99 | }, { 100 | buggyFunctionName: 'AuxiliaryCode.allowLeadingZeroInResult', 101 | ignoredTestSuiteIds: [], 102 | messages: [ 103 | [ 104 | 'Try running your code on 2-digit numbers. Did you get the expected ', 105 | 'result?' 106 | ].join(''), 107 | [ 108 | 'What happens if the number has a leading zero, like 08?', 109 | 'Should it still be included?' 110 | ].join(''), 111 | [ 112 | "It looks like you are allowing numbers with a leading zero", 113 | "when they should not be included." 114 | ].join('') 115 | ] 116 | }], 117 | suiteLevelTests: [], 118 | // Currently, the system does not support nonlinear runtime complexities. 119 | // So we are omitting performance tests for now. 120 | performanceTests: [] 121 | }] 122 | }; 123 | -------------------------------------------------------------------------------- /assets/questions/longestSubstring.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Question data for longest substring with at most two distinct 17 | * characters problem. 18 | */ 19 | 20 | globalData.questions['longestSubstring'] = { // eslint-disable-line dot-notation 21 | title: 'Longest Substring With At Most Two Distinct Characters', 22 | starterCode: { 23 | python: 24 | `def longestSubstring(str): 25 | return "" 26 | ` 27 | }, 28 | auxiliaryCode: { 29 | python: 30 | `class AuxiliaryCode(object): 31 | 32 | @classmethod 33 | def onlyUseOneLetterInResult(cls, word): 34 | longest = '' 35 | for i in range(len(word)): 36 | diff = False 37 | for j in range(i, len(word)): 38 | if word[j] != word[i]: 39 | diff = True 40 | break 41 | if diff: 42 | j -= 1 43 | if len(longest) < (j - i + 1): 44 | longest = word[i:(j + 1)] 45 | return longest 46 | ` 47 | }, 48 | tasks: [{ 49 | id: 'findLongestSubstring', 50 | instructions: [{ 51 | content: [ 52 | "For this problem, you'll be writing a longestSubstring method ", 53 | 'that, given a string, finds the longest substring (continous ', 54 | 'sequence of characters) that contains at ', 55 | 'most 2 distinct characters.' 56 | ].join(''), 57 | type: 'text' 58 | }, { 59 | content: [ 60 | 'Input: "cababadadad"', 61 | 'Output: "adadad"' 62 | ].join('\n'), 63 | type: 'code' 64 | }], 65 | prerequisiteSkills: ['Arrays', 'Strings', 'String Manipulation'], 66 | acquiredSkills: ['String Manipulation'], 67 | inputFunctionName: null, 68 | outputFunctionName: null, 69 | mainFunctionName: 'longestSubstring', 70 | languageSpecificTips: { 71 | python: [] 72 | }, 73 | testSuites: [{ 74 | id: 'GENERAL_CASE', 75 | humanReadableName: 'the general case', 76 | testCases: [{ 77 | input: 'ababcbcbaaabbdef', 78 | allowedOutputs: ['baaabb'] 79 | }, { 80 | input: 'abcbbcbcbb', 81 | allowedOutputs: ['bcbbcbcbb'] 82 | }, { 83 | input: 'cababadadad', 84 | allowedOutputs: ['adadad'] 85 | }] 86 | }, { 87 | id: 'FEW_DISTINCT_CHARACTERS', 88 | humanReadableName: 'strings with at most 2 distinct characters', 89 | testCases: [{ 90 | input: 'bbbbbbbb', 91 | allowedOutputs: ['bbbbbbbb'] 92 | }, { 93 | input: 'ab', 94 | allowedOutputs: ['ab'] 95 | }, { 96 | input: 'abbba', 97 | allowedOutputs: ['abbba'] 98 | }] 99 | }], 100 | buggyOutputTests: [{ 101 | buggyFunctionName: 'AuxiliaryCode.onlyUseOneLetterInResult', 102 | ignoredTestSuiteIds: [], 103 | messages: [ 104 | [ 105 | 'Try running your code on the input, "abbabbabb". ', 106 | 'Is the result what you expected?' 107 | ].join(''), 108 | 'How many types of characters appear in your solution?', 109 | [ 110 | 'For a string that has at least two types of characters, ', 111 | 'the result should have two types of characters, as well. ', 112 | "For example, 'abbba' should return 'abbba', not 'bbb'." 113 | ].join('') 114 | ] 115 | }], 116 | suiteLevelTests: [], 117 | performanceTests: [{ 118 | inputDataAtom: 'oooyyoxaoo', 119 | transformationFunctionName: 'System.extendString', 120 | expectedPerformance: 'linear', 121 | evaluationFunctionName: 'longestSubstring'}] 122 | }] 123 | }; 124 | -------------------------------------------------------------------------------- /assets/tests/CodeCheckerService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Utility service for tests, in order to validate that relevant 17 | * constructs occur in given code snippets. 18 | */ 19 | 20 | tie.factory('CodeCheckerService', [ 21 | function() { 22 | /** 23 | * Validates that a function with the given function name exists in the 24 | * given code. 25 | * 26 | * @param {string} functionName 27 | * @param {string} code 28 | * @returns {boolean} 29 | * @private 30 | */ 31 | // TODO(eyurko): Add check that definition occurs at line start. 32 | var _checkIfFunctionExistsInCode = function(functionName, code) { 33 | var functionDefinition = 'def ' + functionName + '('; 34 | return code.indexOf(functionDefinition) !== -1; 35 | }; 36 | 37 | /** 38 | * Validates that a class definition exists within the given code. 39 | * 40 | * @param {string} className 41 | * @param {string} code 42 | * @returns {boolean} 43 | * @private 44 | */ 45 | var _checkIfClassDefinitionExistsInCode = function(className, code) { 46 | var classDefinitionPattern = new RegExp('class ' + className + 47 | '\\((object)?\\):'); 48 | return code.match(classDefinitionPattern) !== null; 49 | }; 50 | 51 | return { 52 | /** 53 | * Returns results of _checkIfFunctionExistsInCode. 54 | * 55 | * @param {string} functionName 56 | * @param {string} code 57 | * @returns {boolean} 58 | */ 59 | checkIfFunctionExistsInCode: function(functionName, code) { 60 | return _checkIfFunctionExistsInCode(functionName, code); 61 | }, 62 | checkIfClassDefinitionExistsInCode: function(className, code) { 63 | return _checkIfClassDefinitionExistsInCode(className, code); 64 | }, 65 | /** 66 | * Checks if a function exists within a specified class with the specified 67 | * function name in the given code. 68 | * 69 | * @param {string} functionName 70 | * @param {string} className 71 | * @param {string} code 72 | * @returns {boolean} 73 | */ 74 | // TODO(eyurko): Add check that function exists in specified class. 75 | checkIfFunctionExistsInClass: function(functionName, className, code) { 76 | var fullClassNamePrefix = className + '.'; 77 | if (functionName.startsWith(fullClassNamePrefix)) { 78 | var strippedFunctionName = functionName.substring( 79 | fullClassNamePrefix.length); 80 | return _checkIfFunctionExistsInCode(strippedFunctionName, code); 81 | } 82 | return false; 83 | } 84 | }; 85 | } 86 | ]); 87 | -------------------------------------------------------------------------------- /assets/tests/QuestionSchemaValidationServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for the QuestionSchemaValidationService. 17 | */ 18 | 19 | describe('QuestionSchemaValidationService', function() { 20 | var QuestionSchemaValidationService; 21 | var QuestionObjectFactory; 22 | 23 | var questions = []; 24 | // Hardcoded number of functions in QuestionSchemaValidationService. 25 | // Update if you add new question schema tests. 26 | var EXPECTED_VERIFIER_FUNCTION_COUNT = 9; 27 | // Should contain all question IDs. 28 | var QUESTION_IDS = Object.keys(globalData.questions); 29 | 30 | beforeEach(module('tie')); 31 | beforeEach(inject(function($injector) { 32 | QuestionObjectFactory = $injector.get('QuestionObjectFactory'); 33 | QuestionSchemaValidationService = $injector.get( 34 | 'QuestionSchemaValidationService'); 35 | 36 | questions = QUESTION_IDS.map(function(questionId) { 37 | return QuestionObjectFactory.create(globalData.questions[questionId]); 38 | }); 39 | })); 40 | 41 | describe('validateTitlesAreUnique', function() { 42 | it('should verify that all questions have a unique title', function() { 43 | var titles = new Set(); 44 | questions.forEach(function(question) { 45 | titles.add(question.getTitle()); 46 | }); 47 | expect(titles.size).toEqual(questions.length); 48 | }); 49 | }); 50 | 51 | describe('validateAllQuestions', function() { 52 | it('should validate the structure of all sample questions', function() { 53 | var functions = Object.keys(QuestionSchemaValidationService); 54 | questions.forEach(function(question) { 55 | var functionCount = 0; 56 | var title = question.getTitle(); 57 | 58 | functions.forEach(function(verifierFunctionName) { 59 | functionCount++; 60 | var verifierFunction = 61 | QuestionSchemaValidationService[verifierFunctionName]; 62 | expect(verifierFunction(question)).toBe(true, [ 63 | verifierFunctionName, ' failed for the question: "' + title + '"' 64 | ].join('')); 65 | }); 66 | 67 | expect(functionCount).toEqual(EXPECTED_VERIFIER_FUNCTION_COUNT, [ 68 | 'Only ' + functionCount + ' functions were called for ' + title 69 | ].join('')); 70 | }); 71 | }); 72 | 73 | it('checks that question id contains only alphabetic chars', function() { 74 | QUESTION_IDS.forEach(function(questionId, index) { 75 | expect(/^[a-zA-Z]+$/.test(QUESTION_IDS[index])).toBe(true); 76 | }); 77 | }); 78 | }); 79 | }); 80 | 81 | -------------------------------------------------------------------------------- /assets/tests/TaskSchemaValidationServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for the TaskSchemaValidationService. 17 | */ 18 | 19 | describe('TaskSchemaValidationService', function() { 20 | var QuestionObjectFactory; 21 | var TaskSchemaValidationService; 22 | 23 | var questions = []; 24 | // Hardcoded number of functions in TaskSchemaValidationService. 25 | // Update if you add new task schema tests. 26 | var EXPECTED_VERIFIER_FUNCTION_COUNT = 42; 27 | // Should contain all question IDs. 28 | var QUESTION_IDS = Object.keys(globalData.questions); 29 | 30 | beforeEach(module('tie')); 31 | beforeEach(inject(function($injector) { 32 | QuestionObjectFactory = $injector.get('QuestionObjectFactory'); 33 | TaskSchemaValidationService = $injector.get('TaskSchemaValidationService'); 34 | 35 | questions = QUESTION_IDS.map(function(questionId) { 36 | return QuestionObjectFactory.create(globalData.questions[questionId]); 37 | }); 38 | })); 39 | 40 | describe('validateAllTasks', function() { 41 | it('should validate the structure of all sample tasks', function() { 42 | var functions = Object.keys(TaskSchemaValidationService); 43 | questions.forEach(function(question) { 44 | TaskSchemaValidationService.init( 45 | question.getStarterCode('python'), 46 | question.getAuxiliaryCode('python')); 47 | 48 | question.getTasks().forEach(function(task, index) { 49 | var functionCount = 0; 50 | 51 | functions.forEach(function(verifierFunctionName) { 52 | if (verifierFunctionName.indexOf('verify') === 0) { 53 | functionCount++; 54 | var verifierFunction = TaskSchemaValidationService[ 55 | verifierFunctionName]; 56 | expect(verifierFunction(task)).toBe(true, [ 57 | verifierFunctionName, 58 | ' returned false, but it should ', 59 | 'return true for ', 60 | question.getTitle() 61 | ].join('')); 62 | } 63 | }); 64 | 65 | expect(functionCount).toEqual(EXPECTED_VERIFIER_FUNCTION_COUNT, [ 66 | 'Only ' + functionCount + ' functions were called for ', 67 | question.getTitle() + '\'s task at index ' + index 68 | ].join('')); 69 | }); 70 | }); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /client/config/config.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Adds an importable configuration file for TIE. 17 | */ 18 | 19 | window.tieConfig = angular.module('tieConfig', []); 20 | 21 | tieConfig.config(['$httpProvider', function($httpProvider) { 22 | // Set default headers for POST and PUT requests. 23 | $httpProvider.defaults.headers.post = { 24 | 'Content-Type': 'application/x-www-form-urlencoded' 25 | }; 26 | $httpProvider.defaults.headers.put = { 27 | 'Content-Type': 'application/x-www-form-urlencoded' 28 | }; 29 | 30 | // Convert requests to JSON strings. 31 | $httpProvider.interceptors.push([ 32 | '$httpParamSerializerJQLike', function($httpParamSerializerJQLike) { 33 | return { 34 | request: function(config) { 35 | if (config.data) { 36 | config.data = $httpParamSerializerJQLike({ 37 | payload: JSON.stringify(config.data) 38 | }); 39 | } 40 | return config; 41 | } 42 | }; 43 | } 44 | ]); 45 | }]); 46 | 47 | /** 48 | * Setting app to html5mode so that we don't have to have #'s in the url. 49 | */ 50 | tieConfig.config(['$locationProvider', function($locationProvider) { 51 | $locationProvider.html5Mode({ 52 | enabled: true, 53 | requireBase: false 54 | }); 55 | }]); 56 | -------------------------------------------------------------------------------- /client/config/deploymentSpecificConfig.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview This file contains deployment specific constants. 17 | */ 18 | 19 | tieConfig.constant('SERVER_URL', null); 20 | 21 | tieConfig.constant('PRIMER_DIRECTORY_URL', 'docs/'); 22 | 23 | tieConfig.constant('ALLOW_PRINTING', false); 24 | 25 | /** 26 | * Default question ID to use if no qid parameter is specified in the URL. 27 | * 28 | * @type {string} 29 | * @constant 30 | */ 31 | tieConfig.constant('DEFAULT_QUESTION_ID', 'reverseWords'); 32 | 33 | /** 34 | * For the definition of an origin, please see 35 | * https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy. 36 | */ 37 | tieConfig.constant('EXPECTED_PARENT_PAGE_ORIGIN', null); 38 | 39 | /* 40 | * Shows the privacy modal on-click if a URL to a separate privacy page does 41 | * not exist. 42 | */ 43 | tieConfig.constant('PRIVACY_URL', null); 44 | 45 | /* 46 | * If a URL is specified, then a Terms button will appear in the UI. 47 | */ 48 | tieConfig.constant('TERMS_OF_USE_URL', null); 49 | 50 | tieConfig.constant('ABOUT_TIE_LABEL', 'About TIE'); 51 | 52 | tieConfig.constant('ABOUT_TIE_URL', 'https://google.github.io/tie/'); 53 | -------------------------------------------------------------------------------- /client/data/data.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Module for storing question and user data. 17 | */ 18 | 19 | window.globalData = { 20 | /** 21 | * Question data will be stored here, keyed by question ID. 22 | * Questions are instantiated in assets/questions, and they add themselves 23 | * to this dictionary when they're instantiated. 24 | * 25 | * @type {dict} 26 | */ 27 | questions: {} 28 | }; 29 | 30 | window.tieData = angular.module('tieData', []); 31 | 32 | /** 33 | * The current schema version for questions in the client. All questions in 34 | * assets/questions and the checks in assets/tests should always be consistent 35 | * with this schema version. 36 | * 37 | * @type {number} 38 | * @constant 39 | */ 40 | tieData.constant('CURRENT_QUESTION_SCHEMA_VERSION_IN_CLIENT', 1); 41 | 42 | /** 43 | * Class name for wrapping auxiliary code, primarily used for test evaluation. 44 | * 45 | * @type {string} 46 | * @constant 47 | */ 48 | tieData.constant('CLASS_NAME_AUXILIARY_CODE', 'AuxiliaryCode'); 49 | 50 | /** 51 | * Class name for wrapping system code. 52 | * 53 | * @type {string} 54 | * @constant 55 | */ 56 | tieData.constant('CLASS_NAME_SYSTEM_CODE', 'System'); 57 | 58 | /** 59 | * FeedbackParagraph type that will be rendered to look like a normal text 60 | * paragraph. 61 | * 62 | * @type {string} 63 | * @constant 64 | */ 65 | tieData.constant('PARAGRAPH_TYPE_TEXT', 'text'); 66 | 67 | /** 68 | * FeedbackParagraph type that will render text to look like code. 69 | * 70 | * @type {string} 71 | * @constant 72 | */ 73 | tieData.constant('PARAGRAPH_TYPE_CODE', 'code'); 74 | 75 | /** 76 | * FeedbackParagraph type that will render text to bring attention to an error. 77 | * 78 | * @type {string} 79 | * @constant 80 | */ 81 | tieData.constant('PARAGRAPH_TYPE_ERROR', 'error'); 82 | 83 | /** 84 | * FeedbackParagraph type for displaying user code output. 85 | * 86 | * @type {string} 87 | * @constant 88 | */ 89 | tieData.constant('PARAGRAPH_TYPE_OUTPUT', 'output'); 90 | 91 | /** 92 | * FeedbackParagraph type for displaying an image. 93 | * 94 | * @type {string} 95 | * @constant 96 | */ 97 | tieData.constant('PARAGRAPH_TYPE_IMAGE', 'image'); 98 | -------------------------------------------------------------------------------- /client/data/domain/BuggyOutputTestObjectFactory.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Factory for creating new frontend instances of BuggyOutputTest 17 | * domain objects. 18 | */ 19 | 20 | tieData.factory('BuggyOutputTestObjectFactory', [ 21 | function() { 22 | /** 23 | * BuggyOutputTest is used to store all of the information needed to 24 | * complete one test meant to see if the student's code took into 25 | * consideration a common bug. 26 | */ 27 | 28 | /** 29 | * Constructor for BuggyOutputTest 30 | * 31 | * @param {dict} buggyOutputTestDict Contains all the properties needed to 32 | * make a BuggyOutputTest 33 | * @constructor 34 | */ 35 | var BuggyOutputTest = function(buggyOutputTestDict) { 36 | /** 37 | * @type {string} 38 | * @private 39 | */ 40 | this._buggyFunctionName = buggyOutputTestDict.buggyFunctionName; 41 | 42 | /** 43 | * @type {Array} 44 | * @private 45 | */ 46 | this._ignoredTestSuiteIds = buggyOutputTestDict.ignoredTestSuiteIds; 47 | 48 | /** 49 | * @type {Array} 50 | * @private 51 | */ 52 | this._messages = buggyOutputTestDict.messages; 53 | }; 54 | 55 | // Instance methods. 56 | 57 | /** 58 | * Returns the name of the buggy function. 59 | * 60 | * @returns {string} 61 | */ 62 | BuggyOutputTest.prototype.getBuggyFunctionName = function() { 63 | return this._buggyFunctionName; 64 | }; 65 | 66 | /** 67 | * Returns a list of IDs of test suites that should not be considered when 68 | * comparing the output of the learner's code to the output of a buggy 69 | * function. 70 | * 71 | * @returns {Array} 72 | */ 73 | BuggyOutputTest.prototype.getIgnoredTestSuiteIds = function() { 74 | return this._ignoredTestSuiteIds; 75 | }; 76 | 77 | /** 78 | * Returns the list of messages that a buggy output test has attached to it. 79 | * 80 | * @returns {Array} Should be array of strings 81 | */ 82 | BuggyOutputTest.prototype.getMessages = function() { 83 | return this._messages; 84 | }; 85 | 86 | // Static class methods. 87 | /** 88 | * Returns a BuggyOutputTest object from the dictionary specified in 89 | * the parameter 90 | * 91 | * @param {dict} buggyOutputTestDict 92 | * @returns {BuggyOutputTest} 93 | */ 94 | BuggyOutputTest.create = function(buggyOutputTestDict) { 95 | return new BuggyOutputTest(buggyOutputTestDict); 96 | }; 97 | 98 | return BuggyOutputTest; 99 | } 100 | ]); 101 | -------------------------------------------------------------------------------- /client/data/domain/PerformanceTestObjectFactory.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Factory for creating new frontend instances of PerformanceTest 17 | * domain objects. 18 | */ 19 | 20 | tieData.factory('PerformanceTestObjectFactory', [ 21 | function() { 22 | /** 23 | * PerformanceTest encapsulates all of the information needed to 24 | * test if the student's code meets performance expectations. 25 | */ 26 | 27 | /** 28 | * Constructor for PerformanceTest 29 | * 30 | * @param {dict} performanceTestDict 31 | * @constructor 32 | */ 33 | var PerformanceTest = function(performanceTestDict) { 34 | /** 35 | * Input needed to perform this Performance test. 36 | * 37 | * @type: {*} 38 | * @private 39 | */ 40 | this._inputDataAtom = performanceTestDict.inputDataAtom; 41 | 42 | /** 43 | * Name of the transformation function to be used to analyze the code's 44 | * performance 45 | * 46 | * @type: {string} 47 | * @private 48 | */ 49 | this._transformationFunctionName = 50 | performanceTestDict.transformationFunctionName; 51 | 52 | /** 53 | * String describing the expected performance of the code (linear or not 54 | * linear for now) 55 | * 56 | * @type: {string} 57 | * @private 58 | */ 59 | this._expectedPerformance = performanceTestDict.expectedPerformance; 60 | 61 | /** 62 | * Name of the function that is being evaluated in this test. 63 | * 64 | * @type: {string} 65 | * @private 66 | */ 67 | this._evaluationFunctionName = performanceTestDict.evaluationFunctionName; 68 | }; 69 | 70 | // Instance methods. 71 | /** 72 | * A getter for the _transformationFunctionName property. 73 | * Should return a string with the name of the function that will transform 74 | * the code being analyzed. 75 | * 76 | * @returns {string} 77 | */ 78 | PerformanceTest.prototype.getTransformationFunctionName = function() { 79 | return this._transformationFunctionName; 80 | }; 81 | 82 | /** 83 | * A getter for the _inputDataAtom property. 84 | * Should return an object that will be used as an input for this test. 85 | * 86 | * @returns {*} 87 | */ 88 | PerformanceTest.prototype.getInputDataAtom = function() { 89 | return this._inputDataAtom; 90 | }; 91 | 92 | /** 93 | * A getter for the _expectedPerformance property. 94 | * Should return a string that describes the expected performance for the 95 | * evaluated code. 96 | * 97 | * @returns {string} 98 | */ 99 | PerformanceTest.prototype.getExpectedPerformance = function() { 100 | return this._expectedPerformance; 101 | }; 102 | 103 | /** 104 | * A getter for the _evaluationFunctionName property. 105 | * Should return a string with the name of the function that is being 106 | * evaluated in this test. 107 | * 108 | * @returns {string} 109 | */ 110 | PerformanceTest.prototype.getEvaluationFunctionName = function() { 111 | return this._evaluationFunctionName; 112 | }; 113 | 114 | // Static class methods. 115 | /** 116 | * Returns a PerformanceTest object based on the dict passed in as a param. 117 | * 118 | * @param {dict} performanceTestDict Should have all of the properties 119 | * needed to make the PerformanceTest. 120 | * @returns {PerformanceTest} 121 | */ 122 | PerformanceTest.create = function(performanceTestDict) { 123 | return new PerformanceTest(performanceTestDict); 124 | }; 125 | 126 | return PerformanceTest; 127 | } 128 | ]); 129 | -------------------------------------------------------------------------------- /client/data/domain/QuestionObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for QuestionObject domain objects. 17 | */ 18 | 19 | describe('QuestionObjectFactory', function() { 20 | var QuestionObjectFactory; 21 | var question; 22 | var questionWithNoTasks; 23 | var TITLE = "title"; 24 | var STARTER_CODE = "starterCode"; 25 | var AUXILIARY_CODE = "auxiliaryCode"; 26 | var INVALID_LANGUAGE = "invalidLanguage"; 27 | 28 | beforeEach(module('tie')); 29 | beforeEach(module('tieData')); 30 | beforeEach(inject(function($injector) { 31 | QuestionObjectFactory = $injector.get( 32 | 'QuestionObjectFactory'); 33 | question = QuestionObjectFactory.create({ 34 | title: TITLE, 35 | starterCode: STARTER_CODE, 36 | auxiliaryCode: AUXILIARY_CODE, 37 | tasks: [{ 38 | testSuites: [], 39 | buggyOutputTests: [], 40 | suiteLevelTests: [], 41 | performanceTests: [] 42 | }, { 43 | testSuites: [], 44 | buggyOutputTests: [], 45 | suiteLevelTests: [], 46 | performanceTests: [] 47 | }] 48 | }); 49 | questionWithNoTasks = QuestionObjectFactory.create({ 50 | title: TITLE, 51 | starterCode: STARTER_CODE, 52 | auxiliaryCode: AUXILIARY_CODE, 53 | tasks: [] 54 | }); 55 | })); 56 | 57 | describe('getStarterCodeError', function() { 58 | it([ 59 | 'should throw an error when the starter code language ', 60 | 'is invalid' 61 | ].join(''), function() { 62 | var errorFunction = function() { 63 | question.getStarterCode(INVALID_LANGUAGE); 64 | }; 65 | expect(errorFunction) 66 | .toThrowError(Error); 67 | }); 68 | }); 69 | 70 | describe('getAuxiliaryCodeError', function() { 71 | it([ 72 | 'should throw an error when the auxiliary code ', 73 | 'language is invalid' 74 | ].join(''), function() { 75 | var errorFunction = function() { 76 | question.getAuxiliaryCode(INVALID_LANGUAGE); 77 | }; 78 | expect(errorFunction).toThrowError(Error); 79 | }); 80 | }); 81 | 82 | describe('isLastTask', function() { 83 | it([ 84 | 'should return true if the provided index is ', 85 | 'the index of the last task' 86 | ].join(''), function() { 87 | expect(question.isLastTask(2)).toBe(false); 88 | expect(question.isLastTask(-1)).toBe(false); 89 | expect(question.isLastTask(1)).toBe(true); 90 | expect(questionWithNoTasks.isLastTask(1)).toBe(false); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /client/data/domain/SuiteLevelTestObjectFactory.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Factory for creating new frontend instances of SuiteLevelTest 17 | * domain objects. 18 | */ 19 | 20 | tieData.factory('SuiteLevelTestObjectFactory', [ 21 | function() { 22 | /** 23 | * A SuiteLevelTest domain object stores all the information needed to 24 | * complete one test meant to check which suites pass or fail, and give 25 | * feedback accordingly if the correct criteria are met. 26 | */ 27 | 28 | /** 29 | * Constructor for SuiteLevelTest 30 | * 31 | * @param {dict} suiteLevelTestDict Contains all the properties needed to 32 | * make a SuiteLevelTest 33 | * @constructor 34 | */ 35 | var SuiteLevelTest = function(suiteLevelTestDict) { 36 | /** 37 | * @type {string} 38 | * @private 39 | */ 40 | this._testSuiteIdsThatMustPass = ( 41 | suiteLevelTestDict.testSuiteIdsThatMustPass); 42 | 43 | /** 44 | * @type {string} 45 | * @private 46 | */ 47 | this._testSuiteIdsThatMustFail = ( 48 | suiteLevelTestDict.testSuiteIdsThatMustFail); 49 | 50 | /** 51 | * @type {Array} 52 | * @private 53 | */ 54 | this._messages = suiteLevelTestDict.messages; 55 | }; 56 | 57 | // Instance methods. 58 | 59 | /** 60 | * Returns the list of test suite IDs that must pass. This should only be 61 | * used in validation tests; callers should use areConditionsMet() instead 62 | * for running tests. 63 | * 64 | * @returns {Array} Should be array of strings 65 | */ 66 | SuiteLevelTest.prototype.getTestSuiteIdsThatMustPass = function() { 67 | return this._testSuiteIdsThatMustPass; 68 | }; 69 | 70 | /** 71 | * Returns the list of test suite IDs that must fail. This should only be 72 | * used in validation tests; callers should use areConditionsMet() instead 73 | * for running tests. 74 | * 75 | * @returns {Array} Should be array of strings 76 | */ 77 | SuiteLevelTest.prototype.getTestSuiteIdsThatMustFail = function() { 78 | return this._testSuiteIdsThatMustFail; 79 | }; 80 | 81 | /** 82 | * Returns the list of messages attached to this suite-level test. 83 | * 84 | * @returns {Array} Should be array of strings 85 | */ 86 | SuiteLevelTest.prototype.getMessages = function() { 87 | return this._messages; 88 | }; 89 | 90 | /** 91 | * Returns whether this test should trigger, given the full list of suite 92 | * IDs that the learner's code passes fully. 93 | * 94 | * @param {Array} passingSuiteIds. A complete list of test suite IDs passed 95 | * by the learner's submitted code. 96 | * @returns {bool} 97 | */ 98 | SuiteLevelTest.prototype.areConditionsMet = function(passingSuiteIds) { 99 | return this._testSuiteIdsThatMustPass.every(function(suiteId) { 100 | return passingSuiteIds.indexOf(suiteId) !== -1; 101 | }) && this._testSuiteIdsThatMustFail.every(function(suiteId) { 102 | return passingSuiteIds.indexOf(suiteId) === -1; 103 | }); 104 | }; 105 | 106 | // Static class methods. 107 | /** 108 | * Returns a SuiteLevelTest object from the dictionary specified in 109 | * the parameter 110 | * 111 | * @param {dict} suiteLevelTestDict 112 | * @returns {SuiteLevelTest} 113 | */ 114 | SuiteLevelTest.create = function(suiteLevelTestDict) { 115 | return new SuiteLevelTest(suiteLevelTestDict); 116 | }; 117 | 118 | return SuiteLevelTest; 119 | } 120 | ]); 121 | -------------------------------------------------------------------------------- /client/data/domain/SuiteLevelTestObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for QuestionObject domain objects. 17 | */ 18 | 19 | describe('SuiteLevelTestObjectFactory', function() { 20 | var SuiteLevelTestObjectFactory; 21 | var suiteLevelTest; 22 | 23 | beforeEach(module('tie')); 24 | beforeEach(module('tieData')); 25 | beforeEach(inject(function($injector) { 26 | SuiteLevelTestObjectFactory = $injector.get('SuiteLevelTestObjectFactory'); 27 | suiteLevelTest = SuiteLevelTestObjectFactory.create({ 28 | testSuiteIdsThatMustPass: ['SUITE_P1', 'SUITE_P2'], 29 | testSuiteIdsThatMustFail: ['SUITE_F1'], 30 | messages: ['message 1', 'message 2', 'message 3'] 31 | }); 32 | })); 33 | 34 | describe('getSuiteIdsThatMustPass', function() { 35 | it('should correctly retrieve the suite IDs that must pass', function() { 36 | expect(suiteLevelTest.getTestSuiteIdsThatMustPass()).toEqual([ 37 | 'SUITE_P1', 'SUITE_P2']); 38 | }); 39 | }); 40 | 41 | describe('getSuiteIdsThatMustFail', function() { 42 | it('should correctly retrieve the suite IDs that must fail', function() { 43 | expect(suiteLevelTest.getTestSuiteIdsThatMustFail()).toEqual([ 44 | 'SUITE_F1']); 45 | }); 46 | }); 47 | 48 | describe('getMessages', function() { 49 | it('should correctly retrieve the list of messages', function() { 50 | expect(suiteLevelTest.getMessages()).toEqual([ 51 | 'message 1', 'message 2', 'message 3']); 52 | }); 53 | }); 54 | 55 | describe('areConditionsMet', function() { 56 | it('should check whether its triggering preconditions are met', function() { 57 | expect(suiteLevelTest.areConditionsMet( 58 | ['SUITE_P1', 'SUITE_P2'])).toEqual(true); 59 | 60 | // All "must pass" test suites must pass in order for the suite-level 61 | // test to be triggered. 62 | expect(suiteLevelTest.areConditionsMet(['SUITE_P1'])).toEqual(false); 63 | expect(suiteLevelTest.areConditionsMet(['SUITE_P2'])).toEqual(false); 64 | 65 | // All "must fail" test suites must fail in order for the suite-level 66 | // test to be triggered. 67 | expect(suiteLevelTest.areConditionsMet(['SUITE_F1'])).toEqual(false); 68 | expect(suiteLevelTest.areConditionsMet( 69 | ['SUITE_P1', 'SUITE_P2', 'SUITE_F1'])).toEqual(false); 70 | 71 | // It is fine for other test suites not mentioned in the suite-level test 72 | // to pass as well. 73 | expect(suiteLevelTest.areConditionsMet( 74 | ['SUITE_P1', 'SUITE_P2', 'SUITE_WHATEVER'])).toEqual(true); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /client/data/domain/TaskObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for TaskObject domain objects. 17 | */ 18 | 19 | describe('TaskObjectFactory', function() { 20 | var QuestionObjectFactory; 21 | var question; 22 | 23 | beforeEach(module('tie')); 24 | beforeEach(module('tieData')); 25 | beforeEach(inject(function($injector) { 26 | QuestionObjectFactory = $injector.get("QuestionObjectFactory"); 27 | 28 | question = QuestionObjectFactory.create({ 29 | title: 'title', 30 | starterCode: 'starterCode', 31 | tasks: [{ 32 | instructions: [{ 33 | content: 'For this question, you will implement isBalanced().', 34 | type: 'text' 35 | }, { 36 | content: 'Input: "(())"\nOutput: True', 37 | type: 'code' 38 | }], 39 | outputFunctionName: 'AuxiliaryCode.lettersOnly', 40 | testSuites: [], 41 | buggyOutputTests: [], 42 | suiteLevelTests: [], 43 | performanceTests: [] 44 | }, { 45 | instructions: [{ 46 | content: 'some code', 47 | type: 'code' 48 | }, { 49 | content: 'abc', 50 | type: 'text' 51 | }, { 52 | content: 'def', 53 | type: 'text' 54 | }], 55 | outputFunctionName: 'System.extendString', 56 | testSuites: [], 57 | buggyOutputTests: [], 58 | suiteLevelTests: [], 59 | performanceTests: [] 60 | }] 61 | }); 62 | })); 63 | 64 | describe('getOutputFunctionNameWithoutClass', function() { 65 | it('should properly get OutputFunctionName without the class name', 66 | function() { 67 | expect(question.getTasks()[0].getOutputFunctionNameWithoutClass()) 68 | .toEqual('lettersOnly'); 69 | expect(question.getTasks()[1].getOutputFunctionNameWithoutClass()) 70 | .toEqual('extendString'); 71 | }); 72 | }); 73 | 74 | describe('getTextInstructions', function() { 75 | it('should get the text instructions for a task', function() { 76 | expect(question.getTasks()[0].getTextInstructions()) 77 | .toEqual('For this question, you will implement isBalanced(). '); 78 | expect(question.getTasks()[1].getTextInstructions()) 79 | .toEqual('abc def '); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /client/data/domain/TestCaseObjectFactory.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Factory for creating new frontend instances of TestCase 17 | * domain objects. 18 | */ 19 | 20 | 21 | tieData.factory('TestCaseObjectFactory', [ 22 | function() { 23 | /** 24 | * Each TestCase objects represents a test case that is used to test the 25 | * correctness of a student's code submission. 26 | */ 27 | 28 | /** 29 | * Constructor for TestCase 30 | * 31 | * @param {dict} testCaseDict contains all the properties needed to 32 | * make a TestCase object 33 | * @constructor 34 | */ 35 | var TestCase = function(testCaseDict) { 36 | /** 37 | * @type{*} 38 | * @private 39 | */ 40 | this._input = testCaseDict.input; 41 | 42 | /** 43 | * @type{string} 44 | * @private 45 | */ 46 | this._stringifiedInput = JSON.stringify(this._input); 47 | 48 | /** 49 | * @type {Array} 50 | * @private 51 | */ 52 | this._allowedOutputs = testCaseDict.allowedOutputs; 53 | }; 54 | 55 | // Instance methods. 56 | /** 57 | * A getter for the _input property. 58 | * This function should return an object for the input associated with this 59 | * TestCase. 60 | * 61 | * @returns {*} 62 | */ 63 | TestCase.prototype.getInput = function() { 64 | return this._input; 65 | }; 66 | 67 | /** 68 | * A getter for the _stringifiedInput property. 69 | * This function should return a string version of the input associated with 70 | * this test case. 71 | * 72 | * @returns {string} 73 | */ 74 | TestCase.prototype.getStringifiedInput = function() { 75 | return this._stringifiedInput; 76 | }; 77 | 78 | /** 79 | * Checks if the output param matches any of the allowed 80 | * outputs for this test case. 81 | * 82 | * @param {*} output the output being checked 83 | * @returns {boolean} true if output matches, false if not 84 | */ 85 | TestCase.prototype.matchesOutput = function(output) { 86 | var allowedOutputs = this._allowedOutputs; 87 | var target = output; 88 | if (angular.isArray(output)) { 89 | target = JSON.stringify(output); 90 | allowedOutputs = this._allowedOutputs.map(JSON.stringify); 91 | } 92 | return allowedOutputs.some(function(allowedOutput) { 93 | return angular.equals(allowedOutput, target); 94 | }); 95 | }; 96 | 97 | /** 98 | * Returns the first object in the _allowedOutputs array 99 | * 100 | * @returns {*} 101 | */ 102 | TestCase.prototype.getAnyAllowedOutput = function() { 103 | return this._allowedOutputs[0]; 104 | }; 105 | 106 | /** 107 | * A getter for the _allowedOutputs property. 108 | * In contrast to getAnyAllowedOutput, this method returns all of the 109 | * possible allowed outputs. 110 | * 111 | * @returns {Array} 112 | */ 113 | TestCase.prototype.getAllAllowedOutputs = function() { 114 | return this._allowedOutputs; 115 | }; 116 | 117 | // Static class methods. 118 | /** 119 | * Returns a TestCase object built from the properties specified in the 120 | * dictionary parameter. 121 | * 122 | * @param {dict} testCaseDict should specify all the properties necessary 123 | * to make this TestCase. 124 | * @returns {TestCase} 125 | */ 126 | TestCase.create = function(testCaseDict) { 127 | return new TestCase(testCaseDict); 128 | }; 129 | 130 | return TestCase; 131 | } 132 | ]); 133 | -------------------------------------------------------------------------------- /client/data/domain/TestCaseObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for TestCase domain objects. 17 | */ 18 | 19 | describe('TestCaseObjectFactory', function() { 20 | var TestCaseObjectFactory; 21 | 22 | beforeEach(module('tieData')); 23 | beforeEach(inject(function($injector) { 24 | TestCaseObjectFactory = $injector.get('TestCaseObjectFactory'); 25 | })); 26 | 27 | describe('matchesOutput', function() { 28 | it('should correctly match outputs', function() { 29 | var test = TestCaseObjectFactory.create({ 30 | input: 'cat', 31 | allowedOutputs: ['a', 'b'] 32 | }); 33 | 34 | expect(test.matchesOutput('a')).toBe(true); 35 | expect(test.matchesOutput('b')).toBe(true); 36 | expect(test.matchesOutput('c')).toBe(false); 37 | }); 38 | }); 39 | 40 | describe('matchesOutputArray', function() { 41 | it('should correctly match outputs that are arrays', function() { 42 | var test = TestCaseObjectFactory.create({ 43 | input: 'cat', 44 | allowedOutputs: [['c', 'a'], ['c', 't']] 45 | }); 46 | 47 | expect(test.matchesOutput(['c', 'a'])).toBe(true); 48 | expect(test.matchesOutput(['c', 't'])).toBe(true); 49 | expect(test.matchesOutput(['a', 't'])).toBe(false); 50 | }); 51 | }); 52 | 53 | describe('getAnyAllowedOutput', function() { 54 | it('should correctly retrieve an allowed output', function() { 55 | var test = TestCaseObjectFactory.create({ 56 | input: 'cat', 57 | allowedOutputs: ['a', 'b'] 58 | }); 59 | 60 | expect(['a', 'b']).toContain(test.getAnyAllowedOutput()); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /client/data/domain/TestSuiteObjectFactory.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Factory for creating new frontend instances of TestSuite 17 | * domain objects. 18 | */ 19 | 20 | 21 | tieData.factory('TestSuiteObjectFactory', [ 22 | 'TestCaseObjectFactory', function(TestCaseObjectFactory) { 23 | /** 24 | * Each TestSuite object represents a set of test cases that are used to 25 | * test the correctness of a student's code submission. 26 | */ 27 | 28 | /** 29 | * Constructor for TestSuite 30 | * 31 | * @param {dict} testSuiteDict contains all the properties needed to 32 | * make a TestSuite object. 33 | * @constructor 34 | */ 35 | var TestSuite = function(testSuiteDict) { 36 | /** 37 | * @type{*} 38 | * @private 39 | */ 40 | this._id = testSuiteDict.id; 41 | 42 | /** 43 | * @type{string} 44 | * @private 45 | */ 46 | this._humanReadableName = testSuiteDict.humanReadableName; 47 | 48 | /** 49 | * @type {Array} 50 | * @private 51 | */ 52 | this._testCases = testSuiteDict.testCases.map(function(testCase) { 53 | return TestCaseObjectFactory.create(testCase); 54 | }); 55 | }; 56 | 57 | // Instance methods. 58 | /** 59 | * A getter for the _id property. 60 | * This function should return the ID of the test suite. 61 | * 62 | * @returns {*} 63 | */ 64 | TestSuite.prototype.getId = function() { 65 | return this._id; 66 | }; 67 | 68 | /** 69 | * A getter for the _humanReadableName property. 70 | * This function should return a string that describes the role/purpose of 71 | * this test suite in human-readable terms. 72 | * 73 | * @returns {string} 74 | */ 75 | TestSuite.prototype.getHumanReadableName = function() { 76 | return this._humanReadableName; 77 | }; 78 | 79 | /** 80 | * Returns a list of TestCase objects, each representing a test case within 81 | * this test suite. 82 | * 83 | * @returns {Array} 84 | */ 85 | TestSuite.prototype.getTestCases = function() { 86 | return this._testCases; 87 | }; 88 | 89 | // Static class methods. 90 | /** 91 | * Returns a TestSuite object built from the properties specified 92 | * in the dictionary parameter. 93 | * 94 | * @param {dict} testSuiteDict should specify all the properties 95 | * necessary to make this TestSuite 96 | * @returns {TestSuite} 97 | */ 98 | TestSuite.create = function(testSuiteDict) { 99 | return new TestSuite(testSuiteDict); 100 | }; 101 | 102 | return TestSuite; 103 | } 104 | ]); 105 | -------------------------------------------------------------------------------- /client/data/domain/TestSuiteObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for TestSuite domain objects. 17 | */ 18 | 19 | describe('TestSuiteObjectFactory', function() { 20 | var TestCaseObjectFactory; 21 | var TestSuiteObjectFactory; 22 | 23 | beforeEach(module('tieData')); 24 | beforeEach(inject(function($injector) { 25 | TestSuiteObjectFactory = $injector.get('TestSuiteObjectFactory'); 26 | TestCaseObjectFactory = $injector.get('TestCaseObjectFactory'); 27 | })); 28 | 29 | describe('getId', function() { 30 | it('should correctly retrieve the ID of the test suite', function() { 31 | var suite = TestSuiteObjectFactory.create({ 32 | id: 'ID', 33 | humanReadableName: 'human readable name', 34 | testCases: [] 35 | }); 36 | 37 | expect(suite.getId()).toBe('ID'); 38 | }); 39 | }); 40 | 41 | describe('getHumanReadableName', function() { 42 | it('should correctly retrieve the human-readable name', function() { 43 | var suite = TestSuiteObjectFactory.create({ 44 | id: 'ID', 45 | humanReadableName: 'human readable name', 46 | testCases: [] 47 | }); 48 | 49 | expect(suite.getHumanReadableName()).toBe('human readable name'); 50 | }); 51 | }); 52 | 53 | describe('getTestCases', function() { 54 | it('should correctly retrieve the array of test cases', function() { 55 | var suite1 = TestSuiteObjectFactory.create({ 56 | id: 'ID', 57 | humanReadableName: 'human readable name', 58 | testCases: [] 59 | }); 60 | 61 | expect(suite1.getTestCases()).toEqual([]); 62 | 63 | var suite2 = TestSuiteObjectFactory.create({ 64 | id: 'ID', 65 | humanReadableName: 'human readable name', 66 | testCases: [{ 67 | input: 'abc', 68 | allowedOutputs: ['def'] 69 | }] 70 | }); 71 | 72 | expect(suite2.getTestCases().length).toBe(1); 73 | expect(suite2.getTestCases()[0] instanceof TestCaseObjectFactory) 74 | .toBe(true); 75 | expect(suite2.getTestCases()[0].getInput()).toEqual('abc'); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /client/data/domain/TipObjectFactory.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Factory for creating new frontend instances of Tip domain 17 | * objects. 18 | */ 19 | 20 | tieData.factory('TipObjectFactory', [ 21 | 'PrintTerminalService', 22 | function(PrintTerminalService) { 23 | /** 24 | * Constructor for Tip objects. 25 | * 26 | * @param {dict} tipDict 27 | * @constructor 28 | */ 29 | var Tip = function(tipDict) { 30 | /** 31 | * Whether printing needs to be disabled in order for the tip to 32 | * be activated. 33 | * @type: {boolean} 34 | * @private 35 | */ 36 | this._requirePrintToBeDisabled = tipDict.requirePrintToBeDisabled; 37 | 38 | /** 39 | * The regexp to test the student's code against. 40 | * 41 | * @type: {Regex} 42 | * @private 43 | */ 44 | this._regexp = new RegExp(tipDict.regexString); 45 | 46 | /** 47 | * The message to show the learner when the tip is activated. 48 | * 49 | * @type: {string} 50 | * @private 51 | */ 52 | this._message = tipDict.message; 53 | }; 54 | 55 | // Instance methods. 56 | 57 | /** 58 | * A getter for the _requirePrintToBeDisabled property. 59 | * 60 | * @returns {boolean} 61 | */ 62 | Tip.prototype.getRequirePrintToBeDisabled = function() { 63 | return this._requirePrintToBeDisabled; 64 | }; 65 | 66 | /** 67 | * A getter for the _regexp property. 68 | * 69 | * @returns {Regex} 70 | */ 71 | Tip.prototype.getRegexp = function() { 72 | return this._regexp; 73 | }; 74 | 75 | /** 76 | * A getter for the _message property. 77 | * 78 | * @returns {string} 79 | */ 80 | Tip.prototype.getMessage = function() { 81 | return this._message; 82 | }; 83 | 84 | /** 85 | * Whether the given lines of code activate the tip. 86 | * 87 | * @param {Array} codeLines The lines of code to examine. 88 | * @returns {boolean} 89 | */ 90 | Tip.prototype.isActivatedBy = function(codeLines) { 91 | // If the specification requires print to be disabled and print is 92 | // actually supported, this should not activate a print tip. 93 | if (this._requirePrintToBeDisabled && 94 | PrintTerminalService.isPrintingSupported()) { 95 | return false; 96 | } 97 | var that = this; 98 | return codeLines.some(function(line) { 99 | return line.search(that._regexp) !== -1; 100 | }); 101 | }; 102 | 103 | // Static class methods. 104 | /** 105 | * Returns a Tip object based on the dict passed in as a param. 106 | * 107 | * @param {dict} tipDict Should have all of the properties needed to make 108 | * the Tip. 109 | * @returns {Tip} 110 | */ 111 | Tip.create = function(tipDict) { 112 | return new Tip(tipDict); 113 | }; 114 | 115 | return Tip; 116 | } 117 | ]); 118 | -------------------------------------------------------------------------------- /client/data/services/QuestionDataService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Stateless service for retrieving question data from static 17 | * storage, given a question ID. 18 | */ 19 | 20 | tieData.factory('QuestionDataService', [ 21 | '$q', '$http', '$log', 'QuestionObjectFactory', 'SERVER_URL', 22 | function($q, $http, $log, QuestionObjectFactory, SERVER_URL) { 23 | return { 24 | /** 25 | * Asynchronous call to get the data for a question with the given 26 | * question ID. 27 | * 28 | * @param {string} questionId 29 | * @returns {callback} 30 | */ 31 | fetchQuestionAsync: function(questionId) { 32 | if (SERVER_URL) { 33 | return $http.post('/ajax/get_question_data', { 34 | questionId: questionId 35 | }).then( 36 | function(responseData) { 37 | return QuestionObjectFactory.create( 38 | responseData.data.question_data, 39 | responseData.data.question_version); 40 | }, function() { 41 | $log.error('There was an error in retrieving the question.'); 42 | return null; 43 | } 44 | ); 45 | } else { 46 | var deferred = $q.defer(); 47 | var question = null; 48 | // We force question version = 1 for static questions. 49 | var questionVersion = 1; 50 | if (globalData.questions.hasOwnProperty(questionId)) { 51 | question = QuestionObjectFactory.create( 52 | globalData.questions[questionId], questionVersion); 53 | } else { 54 | $log.error('There is no question with ID: ' + questionId); 55 | } 56 | 57 | deferred.resolve(question); 58 | return deferred.promise; 59 | } 60 | } 61 | }; 62 | } 63 | ]); 64 | -------------------------------------------------------------------------------- /client/menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /client/menu/components/MenuQuestionCardDirective.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Directive for the cards on the TIE menu page. 17 | */ 18 | 19 | tieMenu.directive('menuQuestionCard', [function() { 20 | return { 21 | restrict: 'E', 22 | scope: { 23 | questionId: '@' 24 | }, 25 | template: ` 26 | 36 | 69 | `, 70 | controller: ['$scope', 'QuestionDataService', 71 | function($scope, QuestionDataService) { 72 | var MAX_DESCRIPTION_LENGTH = 280; 73 | 74 | var questionPromise = QuestionDataService.fetchQuestionAsync( 75 | $scope.questionId); 76 | 77 | questionPromise.then(function(question) { 78 | $scope.title = question.getTitle(); 79 | 80 | var firstTask = question.getTasks()[0]; 81 | var fullInstructions = firstTask.getTextInstructions(); 82 | if (fullInstructions.length > MAX_DESCRIPTION_LENGTH) { 83 | $scope.textInstructions = ( 84 | fullInstructions.substring(0, MAX_DESCRIPTION_LENGTH) + '...'); 85 | } else { 86 | $scope.textInstructions = fullInstructions; 87 | } 88 | }); 89 | } 90 | ] 91 | }; 92 | }]); 93 | -------------------------------------------------------------------------------- /client/menu/components/MenuViewDirective.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Directive for the TIE menu view. 17 | */ 18 | 19 | tieMenu.directive('menuView', [function() { 20 | return { 21 | restrict: 'E', 22 | scope: {}, 23 | template: ` 24 |
25 |
26 | Welcome to TIE (Technical Interview Exercises)! 27 |
28 |
29 | Click below to get started on a coding exercise: 30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 | 59 | `, 60 | controller: ['$scope', 61 | function($scope) { 62 | // The titles of the questions this menu page is displaying. 63 | $scope.questionIds = [ 64 | 'reverseWords', 65 | 'checkBalancedParentheses', 66 | 'findMostCommonCharacter', 67 | 'isPalindrome', 68 | 'internationalization', 69 | 'runLengthEncoding' 70 | ]; 71 | } 72 | ] 73 | }; 74 | }]); 75 | -------------------------------------------------------------------------------- /client/menu/menu.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Adds an importable configuration file for the TIE menu page. 17 | */ 18 | 19 | window.tieMenu = angular.module('tieMenu', ['tieConfig', 'tieData']); 20 | -------------------------------------------------------------------------------- /client/question/components/CodeSnippetDirective.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Directive for showing code snippets. 17 | */ 18 | 19 | tie.directive('codeSnippet', [function() { 20 | return { 21 | restrict: 'E', 22 | scope: { 23 | getContent: '&content' 24 | }, 25 | template: ` 26 | 27 | {{line}} 28 |
29 |
30 | 31 | View full code 32 | View full code 33 | 34 | 41 | `, 42 | controller: [ 43 | '$scope', 'MonospaceDisplayModalService', 44 | function($scope, MonospaceDisplayModalService) { 45 | /** 46 | * Function to tell whether the monospace display modal is currently 47 | * open. 48 | * 49 | * @type {function} 50 | */ 51 | $scope.isModalOpen = MonospaceDisplayModalService.isDisplayed; 52 | 53 | // The maximum number of lines to show in a code snippet. 54 | $scope.MAX_NUM_LINES_IN_SNIPPET = 3; 55 | 56 | /** 57 | * Array of strings that represents the lines in the piece of code 58 | * that needs to be snippeted. 59 | * 60 | * @type {Array} 61 | */ 62 | $scope.codeLines = []; 63 | 64 | /** 65 | * Array of strings that represents the snippet lines. 66 | * 67 | * @type {Array} 68 | */ 69 | $scope.snippetLines = []; 70 | 71 | /** 72 | * Opens a modal with the full code. 73 | */ 74 | $scope.openCodeModal = function() { 75 | MonospaceDisplayModalService.showModal( 76 | 'Previous Code', $scope.codeLines); 77 | }; 78 | 79 | /** 80 | * Used to change the code snippet whenever the feedback changes. 81 | */ 82 | $scope.$watch($scope.getContent, function(newValue) { 83 | // Replace spaces by non-breaking spaces so that multiple spaces do 84 | // not get collapsed into a single one. 85 | var htmlFormattedContent = newValue.replace(/ /g, '\u00A0'); 86 | $scope.codeLines = htmlFormattedContent.split('\n'); 87 | 88 | $scope.snippetLines = $scope.codeLines.slice( 89 | 0, $scope.MAX_NUM_LINES_IN_SNIPPET); 90 | if ($scope.codeLines.length > $scope.MAX_NUM_LINES_IN_SNIPPET) { 91 | $scope.snippetLines.push('...'); 92 | } 93 | }); 94 | } 95 | ] 96 | }; 97 | }]); 98 | -------------------------------------------------------------------------------- /client/question/components/ErrorSnippetDirective.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Directive for showing syntax and runtime error snippets. 17 | */ 18 | 19 | tie.directive('errorSnippet', [function() { 20 | return { 21 | restrict: 'E', 22 | scope: { 23 | getContent: '&content' 24 | }, 25 | template: ` 26 |
27 |

28 | If you cannot figure out the problem, you can click on 29 | this link 30 | this link 31 | to display the error message. 32 |

33 |
34 |
35 | 36 | {{line}} 37 |
38 |
39 |
40 | 56 | `, 57 | controller: [ 58 | '$scope', 'MonospaceDisplayModalService', 59 | function($scope, MonospaceDisplayModalService) { 60 | /** 61 | * Function to tell whether the monospace display modal is currently 62 | * open. 63 | * 64 | * @type {function} 65 | */ 66 | $scope.isModalOpen = MonospaceDisplayModalService.isDisplayed; 67 | 68 | /** 69 | * Array of strings used to represent the code snippet lines to be 70 | * presented in the UI. 71 | * 72 | * @type {Array} 73 | */ 74 | $scope.snippetLines = []; 75 | 76 | /** 77 | * Represents whether a syntax error is being shown or not. 78 | * 79 | * @type {boolean} 80 | */ 81 | $scope.syntaxErrorIsShown = false; 82 | 83 | /** 84 | * Opens a modal with the syntax error. 85 | */ 86 | $scope.openSyntaxErrorModal = function() { 87 | MonospaceDisplayModalService.showModal( 88 | 'Error Message', $scope.snippetLines); 89 | }; 90 | 91 | /** 92 | * Used to change the code snippet shown when the feedback changes. 93 | */ 94 | $scope.$watch($scope.getContent, function(newValue) { 95 | // Replace spaces by non-breaking spaces so that multiple spaces do 96 | // not get collapsed into a single one. 97 | var htmlFormattedContent = newValue.replace(/ /g, '\u00A0'); 98 | $scope.snippetLines = htmlFormattedContent.split('\n'); 99 | }); 100 | } 101 | ] 102 | }; 103 | }]); 104 | -------------------------------------------------------------------------------- /client/question/components/HtmlWithMarkdownLinksSnippetDirective.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Directive for showing HTML with Markdown links. For Markdown 17 | * links, only links to HTTPS resources or the Python primer are permitted. 18 | */ 19 | 20 | tie.directive('htmlWithMarkdownLinksSnippet', [function() { 21 | return { 22 | restrict: 'E', 23 | scope: { 24 | // This contains HTML, in addition to Markdown links in the format 25 | // [link-text](link-destination). 26 | getContent: '&content' 27 | }, 28 | template: ` 29 | 30 | `, 31 | controller: [ 32 | '$scope', 'ThemeNameService', 33 | function($scope, ThemeNameService) { 34 | $scope.$watch($scope.getContent, function(newValue) { 35 | // The ng-bind-html attribute sanitizes HTML by default. See 36 | // https://docs.angularjs.org/api/ng/service/$sce 37 | $scope.unsafeHtmlWithLinks = newValue.replace( 38 | /\[([^[\]]+)\]\(([^)]+)\)/g, 39 | function(match, p1, p2) { 40 | var startsWithHttps = (p2.indexOf('https://') === 0); 41 | // The URL for the python primer needs to be special-cased so that 42 | // we can dynamically link to the content, as the primer docs are 43 | // stored in different locations depending on whether you're using 44 | // the open-source version or a hosted version. 45 | var goesToPrimer = (p2.indexOf('primer-url') === 0); 46 | 47 | var targetUrl = null; 48 | if (goesToPrimer) { 49 | var pythonPrimerUrl = ThemeNameService.getPythonPrimerUrl(); 50 | 51 | var hashIndex = p2.indexOf('#'); 52 | if (p2.indexOf('#') === -1) { 53 | targetUrl = pythonPrimerUrl; 54 | } else { 55 | targetUrl = pythonPrimerUrl + p2.substring(hashIndex); 56 | } 57 | } else { 58 | targetUrl = p2; 59 | } 60 | 61 | if (startsWithHttps || goesToPrimer) { 62 | return ( 63 | '' + p1 + ''); 64 | } else { 65 | return ''; 66 | } 67 | }); 68 | }); 69 | } 70 | ] 71 | }; 72 | }]); 73 | -------------------------------------------------------------------------------- /client/question/components/OutputSnippetDirective.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Directive for showing output snippets. 17 | */ 18 | 19 | tie.directive('outputSnippet', [function() { 20 | return { 21 | restrict: 'E', 22 | scope: { 23 | getContent: '&content' 24 | }, 25 | template: ` 26 |
27 | You can click on 28 | this link 29 | this link 30 | to display the input, expected output and actual output. 31 |
32 | 41 | `, 42 | controller: [ 43 | '$scope', 'MonospaceDisplayModalService', 44 | function($scope, MonospaceDisplayModalService) { 45 | /** 46 | * Function to tell whether the monospace display modal is currently 47 | * open. 48 | * 49 | * @type {function} 50 | */ 51 | $scope.isModalOpen = MonospaceDisplayModalService.isDisplayed; 52 | 53 | /** 54 | * Array of strings used to represent the output snippet lines to be 55 | * presented in the UI. 56 | * 57 | * @type {Array} 58 | */ 59 | $scope.snippetLines = []; 60 | 61 | /** 62 | * Represents whether user code output is being shown or not. 63 | * 64 | * @type {boolean} 65 | */ 66 | $scope.outputIsShown = false; 67 | 68 | /** 69 | * Opens a modal with the printed output. 70 | */ 71 | $scope.openOutputModal = function() { 72 | MonospaceDisplayModalService.showModal( 73 | 'Code Output', $scope.snippetLines); 74 | }; 75 | 76 | /** 77 | * Used to change the output snippet shown when the feedback changes. 78 | */ 79 | $scope.$watch($scope.getContent, function(newValue) { 80 | // Replace spaces by non-breaking spaces so that multiple spaces do 81 | // not get collapsed into a single one. 82 | var htmlFormattedContent = newValue.replace(/ /g, '\u00A0'); 83 | $scope.snippetLines = htmlFormattedContent.split('\n'); 84 | }); 85 | } 86 | ] 87 | }; 88 | }]); 89 | -------------------------------------------------------------------------------- /client/question/components/SpeechBalloonDirectives.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Directives for displaying speech balloons. 17 | */ 18 | 19 | tie.directive('tieSpeechBalloonContainer', ['$timeout', 'DELAY_STYLE_CHANGES', 20 | function($timeout, DELAY_STYLE_CHANGES) { 21 | return { 22 | restrict: 'E', 23 | scope: {}, 24 | link: function(scope, element) { 25 | var speechBalloonContainerElement = element[0]; 26 | var speechBalloonContainer = 27 | angular.element(speechBalloonContainerElement); 28 | speechBalloonContainer.addClass('tie-speech-balloon-container'); 29 | speechBalloonContainerElement.style.opacity = '0'; 30 | speechBalloonContainerElement.style.transition = 'unset'; 31 | speechBalloonContainerElement.style.display = 'none'; 32 | $timeout(function() { 33 | speechBalloonContainerElement.style.display = 'block'; 34 | speechBalloonContainerElement.style.marginTop = 35 | '-' + speechBalloonContainerElement.offsetHeight.toString() + 36 | 'px'; 37 | $timeout(function() { 38 | speechBalloonContainerElement.removeAttribute('style'); 39 | }, DELAY_STYLE_CHANGES); 40 | }, 0); 41 | } 42 | }; 43 | } 44 | ]); 45 | 46 | tie.directive('tieSpeechBalloonLeft', ['$timeout', function($timeout) { 47 | return { 48 | restrict: 'E', 49 | scope: {}, 50 | link: function(scope, element) { 51 | var speechBalloon = angular.element(element[0]); 52 | speechBalloon.addClass('tie-speech-balloon tie-speech-balloon-left'); 53 | $timeout(function() { 54 | speechBalloon.addClass('tie-speech-balloon-pulse'); 55 | }, 0); 56 | } 57 | }; 58 | }]); 59 | 60 | tie.directive('tieSpeechBalloonRight', ['$timeout', function($timeout) { 61 | return { 62 | restrict: 'E', 63 | scope: {}, 64 | link: function(scope, element) { 65 | var speechBalloon = angular.element(element[0]); 66 | speechBalloon.addClass('tie-speech-balloon tie-speech-balloon-right'); 67 | $timeout(function() { 68 | speechBalloon.addClass('tie-speech-balloon-pulse'); 69 | }, 0); 70 | } 71 | }; 72 | }]); 73 | 74 | tie.directive('tieSpeechBalloonTailLeft', ['$timeout', function($timeout) { 75 | return { 76 | restrict: 'E', 77 | scope: {}, 78 | template: ` 79 |
80 |
81 |
82 |
83 | `, 84 | link: function(scope, element) { 85 | var speechBalloonTailLeftInner = 86 | angular.element(element[0].getElementsByClassName( 87 | 'tie-speech-balloon-tail-left-inner')[0]); 88 | $timeout(function() { 89 | speechBalloonTailLeftInner.addClass( 90 | 'tie-speech-balloon-tail-left-pulse'); 91 | }, 0); 92 | } 93 | }; 94 | }]); 95 | 96 | tie.directive('tieSpeechBalloonTailRight', ['$timeout', function($timeout) { 97 | return { 98 | restrict: 'E', 99 | scope: {}, 100 | template: ` 101 |
102 |
103 |
104 |
105 | `, 106 | link: function(scope, element) { 107 | var speechBalloonTailRightInner = 108 | angular.element(element[0].getElementsByClassName( 109 | 'tie-speech-balloon-tail-right-inner')[0]); 110 | $timeout(function() { 111 | speechBalloonTailRightInner.addClass( 112 | 'tie-speech-balloon-tail-right-pulse'); 113 | }, 0); 114 | } 115 | }; 116 | }]); 117 | -------------------------------------------------------------------------------- /client/question/domain/CodeSubmissionObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for CodeSubmission domain objects. 17 | */ 18 | 19 | describe('CodeSubmissionObjectFactory', function() { 20 | var CodeSubmissionObjectFactory; 21 | 22 | beforeEach(module('tie')); 23 | beforeEach(inject(function($injector) { 24 | CodeSubmissionObjectFactory = $injector.get('CodeSubmissionObjectFactory'); 25 | })); 26 | 27 | describe('replace', function() { 28 | it('should correctly replace code', function() { 29 | var originalCode = 'line 1\nline 2\nline 3'; 30 | var codeSubmission = CodeSubmissionObjectFactory.create(originalCode); 31 | 32 | expect(codeSubmission.getRawCode()).toEqual(originalCode); 33 | expect(codeSubmission.getPreprocessedCode()).toEqual( 34 | 'line 1\nline 2\nline 3'); 35 | expect(codeSubmission.getRawCodeLineIndexes()).toEqual([0, 1, 2]); 36 | 37 | codeSubmission.replace('newline 1\nnewline 2\nnewline 3'); 38 | 39 | expect(codeSubmission.getRawCode()).toEqual(originalCode); 40 | expect(codeSubmission.getPreprocessedCode()).toEqual( 41 | 'newline 1\nnewline 2\nnewline 3'); 42 | expect(codeSubmission.getRawCodeLineIndexes()).toEqual([0, 1, 2]); 43 | }); 44 | 45 | it('should throw an error if the number of lines changes', function() { 46 | var originalCode = 'line 1\nline 2\nline 3'; 47 | var codeSubmission = CodeSubmissionObjectFactory.create(originalCode); 48 | 49 | expect(codeSubmission.getRawCode()).toEqual(originalCode); 50 | expect(codeSubmission.getPreprocessedCode()).toEqual( 51 | 'line 1\nline 2\nline 3'); 52 | expect(codeSubmission.getRawCodeLineIndexes()).toEqual([0, 1, 2]); 53 | 54 | expect(function() { 55 | codeSubmission.replace('this code has too few lines'); 56 | }).toThrow(); 57 | expect(function() { 58 | codeSubmission.replace('this\ncode\nhas\ntoo\nmany\nlines'); 59 | }).toThrow(); 60 | }); 61 | }); 62 | 63 | describe('append', function() { 64 | it('should correctly append code', function() { 65 | var originalCode = 'line 1\nline 2\nline 3'; 66 | var codeSubmission = CodeSubmissionObjectFactory.create(originalCode); 67 | 68 | expect(codeSubmission.getRawCode()).toEqual(originalCode); 69 | expect(codeSubmission.getPreprocessedCode()).toEqual( 70 | 'line 1\nline 2\nline 3'); 71 | expect(codeSubmission.getRawCodeLineIndexes()).toEqual([0, 1, 2]); 72 | 73 | codeSubmission.append('line 4\nline 5'); 74 | 75 | expect(codeSubmission.getRawCode()).toEqual(originalCode); 76 | expect(codeSubmission.getPreprocessedCode()).toEqual( 77 | 'line 1\nline 2\nline 3\nline 4\nline 5'); 78 | expect(codeSubmission.getRawCodeLineIndexes()).toEqual([ 79 | 0, 1, 2, null, null]); 80 | }); 81 | }); 82 | 83 | describe('prepend', function() { 84 | it('should correctly prepend code', function() { 85 | var originalCode = 'line 1\nline 2\nline 3'; 86 | var codeSubmission = CodeSubmissionObjectFactory.create(originalCode); 87 | 88 | expect(codeSubmission.getRawCode()).toEqual(originalCode); 89 | expect(codeSubmission.getPreprocessedCode()).toEqual( 90 | 'line 1\nline 2\nline 3'); 91 | expect(codeSubmission.getRawCodeLineIndexes()).toEqual([0, 1, 2]); 92 | 93 | codeSubmission.prepend('line -1\nline 0'); 94 | 95 | expect(codeSubmission.getRawCode()).toEqual(originalCode); 96 | expect(codeSubmission.getPreprocessedCode()).toEqual( 97 | 'line -1\nline 0\nline 1\nline 2\nline 3'); 98 | expect(codeSubmission.getRawCodeLineIndexes()).toEqual([ 99 | null, null, 0, 1, 2]); 100 | }); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /client/question/domain/FeedbackObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for FeedbackObject domain objects. 17 | */ 18 | 19 | describe('FeedbackObjectFactory', function() { 20 | var PARAGRAPH_TYPE_TEXT; 21 | var PARAGRAPH_TYPE_CODE; 22 | var PARAGRAPH_TYPE_ERROR; 23 | var PARAGRAPH_TYPE_OUTPUT; 24 | var PARAGRAPH_TYPE_IMAGE; 25 | var FEEDBACK_CATEGORIES; 26 | 27 | var FeedbackObjectFactory; 28 | var feedback; 29 | 30 | beforeEach(module('tie')); 31 | beforeEach(inject(function($injector) { 32 | PARAGRAPH_TYPE_TEXT = $injector.get('PARAGRAPH_TYPE_TEXT'); 33 | PARAGRAPH_TYPE_CODE = $injector.get('PARAGRAPH_TYPE_CODE'); 34 | PARAGRAPH_TYPE_ERROR = $injector.get('PARAGRAPH_TYPE_ERROR'); 35 | PARAGRAPH_TYPE_OUTPUT = $injector.get('PARAGRAPH_TYPE_OUTPUT'); 36 | PARAGRAPH_TYPE_IMAGE = $injector.get('PARAGRAPH_TYPE_IMAGE'); 37 | FEEDBACK_CATEGORIES = $injector.get('FEEDBACK_CATEGORIES'); 38 | 39 | FeedbackObjectFactory = $injector.get('FeedbackObjectFactory'); 40 | feedback = FeedbackObjectFactory.create(FEEDBACK_CATEGORIES.SUCCESSFUL); 41 | })); 42 | 43 | describe('isAnswerCorrect', function() { 44 | it('should validate the feedback category', function() { 45 | expect(function() { 46 | FeedbackObjectFactory.create(FEEDBACK_CATEGORIES.INVALID_CATEGORY); 47 | }).toThrowError('Invalid feedback category: undefined'); 48 | }); 49 | }); 50 | 51 | describe('isAnswerCorrect', function() { 52 | it('should return whether or not the answer is correct', function() { 53 | expect(feedback.isAnswerCorrect()).toBe(true); 54 | }); 55 | }); 56 | 57 | describe('getFeedbackCategory', function() { 58 | it('should return the feedback category', function() { 59 | expect(feedback.getFeedbackCategory()).toBe( 60 | FEEDBACK_CATEGORIES.SUCCESSFUL); 61 | }); 62 | }); 63 | 64 | describe('firstParagraphText', function() { 65 | it('should throw an error when adding paragraph one as code', function() { 66 | var errorFunction = function() { 67 | feedback.appendCodeParagraph("code"); 68 | }; 69 | expect(errorFunction).toThrowError(Error); 70 | }); 71 | }); 72 | 73 | describe('getParagraphsAsListOfDicts', function() { 74 | it('should return an array of paragraphs as dictionaries', function() { 75 | feedback.appendTextParagraph('This'); 76 | feedback.appendCodeParagraph('is'); 77 | feedback.appendErrorParagraph('fine'); 78 | feedback.appendOutputParagraph(':-)'); 79 | feedback.appendImageParagraph('image.png'); 80 | var dictionaries = feedback.getParagraphsAsListOfDicts(); 81 | expect(dictionaries.length).toEqual(5); 82 | expect(dictionaries[0].content).toEqual('This'); 83 | expect(dictionaries[0].type).toEqual(PARAGRAPH_TYPE_TEXT); 84 | expect(dictionaries[1].content).toEqual('is'); 85 | expect(dictionaries[1].type).toEqual(PARAGRAPH_TYPE_CODE); 86 | expect(dictionaries[2].content).toEqual('fine'); 87 | expect(dictionaries[2].type).toEqual(PARAGRAPH_TYPE_ERROR); 88 | expect(dictionaries[3].content).toEqual(':-)'); 89 | expect(dictionaries[3].type).toEqual(PARAGRAPH_TYPE_OUTPUT); 90 | expect(dictionaries[4].content).toEqual('image.png'); 91 | expect(dictionaries[4].type).toEqual(PARAGRAPH_TYPE_IMAGE); 92 | }); 93 | }); 94 | 95 | describe('clearParagraphs', function() { 96 | it('should clear all paragraphs in the current feedback', function() { 97 | feedback.appendTextParagraph('text'); 98 | feedback.appendCodeParagraph('code'); 99 | feedback.appendErrorParagraph('error'); 100 | feedback.appendOutputParagraph('output'); 101 | feedback.clear(); 102 | expect(feedback.getParagraphs.length).toEqual(0); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /client/question/domain/LearnerViewSubmissionResultObjectFactory.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Factory for creating new frontend instances of 17 | * LearnerViewSubmissionResult domain objects. 18 | */ 19 | 20 | tie.factory('LearnerViewSubmissionResultObjectFactory', [ 21 | function() { 22 | /** LearnerViewSubmissionResult stores the feedback and stdout for a 23 | * given code submission. This information is later displayed in 24 | * the feedback window and print terminal after a learner chooses to 25 | * submit their code. 26 | */ 27 | 28 | /** 29 | * Constructor for LearnerViewSubmissionResult 30 | * 31 | * @param {Feedback} feedback The feedback generated after running the 32 | * user code 33 | * @param {string} stdout Output from user code 34 | */ 35 | var LearnerViewSubmissionResult = function(feedback, stdout) { 36 | /** 37 | * @type {Feedback} 38 | * @private 39 | */ 40 | this._feedback = feedback; 41 | 42 | /** 43 | * @type {string} 44 | * @private 45 | */ 46 | this._stdout = stdout; 47 | }; 48 | 49 | // Instance methods. 50 | /** 51 | * A getter for the _feedback property. 52 | * It should return a Feedback object with the correct feedback for 53 | * the submission of the user code. 54 | * 55 | * @returns {Feedback} 56 | */ 57 | LearnerViewSubmissionResult.prototype.getFeedback = function() { 58 | return this._feedback; 59 | }; 60 | 61 | /** 62 | * A getter for the _stdout property. 63 | * It should return a string with the print output of the code submission. 64 | * The stdout displayed is the output associated with the first failed 65 | * test. If all tests for the past and current tasks passed, then the 66 | * output of the last test case of the current task is shown. 67 | * 68 | * @returns {string} 69 | */ 70 | LearnerViewSubmissionResult.prototype.getStdout = function() { 71 | return this._stdout; 72 | }; 73 | 74 | // Static class methods. 75 | /** 76 | * This method creates and returns a LearnerViewSubmissionResult object 77 | * from the params specified. 78 | * 79 | * @param {Feedback} feedback The feedback generated after running the 80 | * user code 81 | * @param {string} stdout Output from user code 82 | * @returns {LearnerViewSubmissionResult} 83 | */ 84 | LearnerViewSubmissionResult.create = function( 85 | feedback, stdout) { 86 | return new LearnerViewSubmissionResult(feedback, stdout); 87 | }; 88 | 89 | return LearnerViewSubmissionResult; 90 | } 91 | ]); 92 | -------------------------------------------------------------------------------- /client/question/domain/LearnerViewSubmissionResultObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for LearnerViewSubmissionResult domain objects. 17 | */ 18 | 19 | describe('LearnerViewSubmissionResultObjectFactory', function() { 20 | var LearnerViewSubmissionResultObjectFactory; 21 | var FeedbackGeneratorService; 22 | 23 | beforeEach(module('tie')); 24 | beforeEach(inject(function($injector) { 25 | LearnerViewSubmissionResultObjectFactory = $injector.get( 26 | 'LearnerViewSubmissionResultObjectFactory'); 27 | FeedbackGeneratorService = $injector.get('FeedbackGeneratorService'); 28 | })); 29 | 30 | describe('getFeedback', function() { 31 | it('should retrieve the corresponding feedback for code with no errors', 32 | function() { 33 | var feedback = FeedbackGeneratorService.getSuccessFeedback(); 34 | var learnerViewSubmissionResult = 35 | LearnerViewSubmissionResultObjectFactory.create( 36 | feedback, 'some output'); 37 | expect(learnerViewSubmissionResult.getFeedback()).toEqual(feedback); 38 | }); 39 | 40 | it('should retrieve the corresponding feedback for error feedback', 41 | function() { 42 | var feedback = FeedbackGeneratorService.getTimeoutErrorFeedback(); 43 | var learnerViewSubmissionResult = 44 | LearnerViewSubmissionResultObjectFactory.create( 45 | feedback, null); 46 | expect(learnerViewSubmissionResult.getFeedback()).toEqual(feedback); 47 | }); 48 | }); 49 | 50 | describe('getStdout', function() { 51 | it('should retrive the corresponding stdout for code with no errors', 52 | function() { 53 | var feedback = FeedbackGeneratorService.getSuccessFeedback(); 54 | var learnerViewSubmissionResult = 55 | LearnerViewSubmissionResultObjectFactory.create( 56 | feedback, 'some output'); 57 | expect(learnerViewSubmissionResult.getStdout()).toEqual('some output'); 58 | }); 59 | 60 | it('should retrieve the corresponding stdout for error feedback', 61 | function() { 62 | var feedback = FeedbackGeneratorService.getTimeoutErrorFeedback(); 63 | var learnerViewSubmissionResult = 64 | LearnerViewSubmissionResultObjectFactory.create( 65 | feedback, null); 66 | expect(learnerViewSubmissionResult.getStdout()).toEqual(null); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /client/question/domain/PreprocessedCodeObjectFactory.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Factory for creating new frontend instances of 17 | * PreprocessedCode domain objects. 18 | */ 19 | 20 | tie.factory('PreprocessedCodeObjectFactory', [ 21 | function() { 22 | /** 23 | * PreprocessedCode stores the entire preprocessed code, raw input code, 24 | * and the string separator generated during print output. This string 25 | * separator is utilized in splitting the total output into the 26 | * corresponding outputs for each test case. 27 | */ 28 | 29 | /** 30 | * Constructor for PreprocessedCode 31 | * 32 | * @param {string} preprocessedCodeString Preprocessed student code 33 | * @param {string} rawCode Student's input code 34 | * @param {string} separator Output separator 35 | */ 36 | var PreprocessedCode = function(preprocessedCodeString, rawCode, 37 | separator) { 38 | /** 39 | * @type {string} 40 | * @private 41 | */ 42 | this._preprocessedCodeString = preprocessedCodeString; 43 | 44 | /** 45 | * @type {string} 46 | * @private 47 | */ 48 | this._rawCode = rawCode; 49 | 50 | /** 51 | * @type {string} 52 | * @private 53 | */ 54 | this._separator = separator; 55 | }; 56 | 57 | // Instance methods. 58 | 59 | /** 60 | * A getter for the _preprocessedCodeString property. 61 | * It should return a string with code that has already been 62 | * preprocessed. 63 | * 64 | * @returns {string} 65 | */ 66 | PreprocessedCode.prototype.getPreprocessedCodeString = function() { 67 | return this._preprocessedCodeString; 68 | }; 69 | 70 | /** 71 | * A getter for the _rawCode property. 72 | * It should return a string with the raw input code that the 73 | * student submitted. 74 | * 75 | * @returns {string} 76 | */ 77 | PreprocessedCode.prototype.getRawCode = function() { 78 | return this._rawCode; 79 | }; 80 | 81 | /** 82 | * A getter for the _separator property. 83 | * It should return a string with the randomly generated separator 84 | * value. 85 | * 86 | * @returns {string} 87 | */ 88 | PreprocessedCode.prototype.getSeparator = function() { 89 | return this._separator; 90 | }; 91 | 92 | // Static class methods. 93 | /** 94 | * This method creates and returns a PreprocessedCode object from 95 | * the params specified. 96 | * 97 | * @param {string} preprocessedCodeString Preprocessed student code 98 | * @param {string} rawCode Raw student input code 99 | * @param {string} separator Output separator 100 | * @returns {PreprocessedCode} 101 | */ 102 | PreprocessedCode.create = function(preprocessedCodeString, rawCode, 103 | separator) { 104 | return new PreprocessedCode(preprocessedCodeString, rawCode, separator); 105 | }; 106 | 107 | return PreprocessedCode; 108 | } 109 | ]); 110 | -------------------------------------------------------------------------------- /client/question/domain/PreprocessedCodeObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for PreprocessedCode domain objects. 17 | */ 18 | 19 | describe('PreprocessedCodeObjectFactory', function() { 20 | var PreprocessedCodeObjectFactory; 21 | 22 | beforeEach(module('tie')); 23 | beforeEach(inject(function($injector) { 24 | PreprocessedCodeObjectFactory = $injector.get( 25 | 'PreprocessedCodeObjectFactory'); 26 | })); 27 | 28 | describe('getPreprocessedCodeString', function() { 29 | it('should retrieve the corresponding preprocessed code', function() { 30 | var rawCode = [ 31 | 'def studentCode():', 32 | ' a = 3', 33 | ' return a' 34 | ].join('\n'); 35 | var preprocessedCode = [ 36 | 'class StudentCode(object):', 37 | ' ' + rawCode, 38 | '', 39 | 'class AuxiliaryCode(object):', 40 | ' @classmethod', 41 | ' def _helperMethods():', 42 | ' return True' 43 | ].join('\n'); 44 | var preprocessedCodeObject = PreprocessedCodeObjectFactory.create( 45 | preprocessedCode, rawCode, 'separator'); 46 | 47 | expect(preprocessedCodeObject.getPreprocessedCodeString()).toEqual( 48 | preprocessedCode); 49 | }); 50 | }); 51 | 52 | describe('getRawCode', function() { 53 | it('should retrieve the corresponding raw code', function() { 54 | var rawCode = [ 55 | 'def studentCode():', 56 | ' a = 3', 57 | ' return a' 58 | ].join('\n'); 59 | var preprocessedCode = [ 60 | 'class StudentCode(object):', 61 | ' ' + rawCode, 62 | '', 63 | 'class AuxiliaryCode(object):', 64 | ' @classmethod', 65 | ' def _helperMethods():', 66 | ' return True' 67 | ].join('\n'); 68 | var separator = 'ABCDEFGHIJKLMNOPQRST'; 69 | var preprocessedCodeObject = PreprocessedCodeObjectFactory.create( 70 | preprocessedCode, rawCode, separator); 71 | 72 | expect(preprocessedCodeObject.getRawCode()).toEqual(rawCode); 73 | }); 74 | }); 75 | 76 | describe('getSeparator', function() { 77 | it('should retrieve the corresponding separator', function() { 78 | var rawCode = [ 79 | 'def studentCode():', 80 | ' a = 3', 81 | ' return a' 82 | ].join('\n'); 83 | var preprocessedCode = [ 84 | 'class StudentCode(object):', 85 | ' ' + rawCode, 86 | '', 87 | 'class AuxiliaryCode(object):', 88 | ' @classmethod', 89 | ' def _helperMethods():', 90 | ' return True' 91 | ].join('\n'); 92 | var separator = 'ABCDEFGHIJKLMNOPQRST'; 93 | var preprocessedCodeObject = PreprocessedCodeObjectFactory.create( 94 | preprocessedCode, rawCode, separator); 95 | 96 | expect(preprocessedCodeObject.getSeparator()).toEqual(separator); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /client/question/domain/PrereqCheckErrorObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for PrereqCheckErrorObjectFactory domain objects. 17 | */ 18 | 19 | describe('PrereqCheckErrorObjectFactory', function() { 20 | var PrereqCheckErrorObjectFactory; 21 | 22 | beforeEach(module('tie')); 23 | beforeEach(inject(function($injector) { 24 | PrereqCheckErrorObjectFactory = $injector.get( 25 | 'PrereqCheckErrorObjectFactory'); 26 | })); 27 | var error = { 28 | errorName: 'notOp', 29 | errorLineNumber: 2, 30 | errorColumnNumber: 20 31 | }; 32 | var PrereqCheckError; 33 | 34 | describe('create', function() { 35 | it('should generate an object with name, line & column number', function() { 36 | PrereqCheckError = PrereqCheckErrorObjectFactory.create( 37 | error.errorName, error.errorLineNumber, error.errorColumnNumber); 38 | 39 | expect(PrereqCheckError.getErrorName()).toEqual(error.errorName); 40 | expect(PrereqCheckError.getErrorLineNumber()).toEqual( 41 | error.errorLineNumber); 42 | expect(PrereqCheckError.getErrorColumnNumber()).toEqual( 43 | error.errorColumnNumber); 44 | }); 45 | 46 | it('should result in a fixed errorType', function() { 47 | expect(PrereqCheckError._errorType).toEqual('wrongLang'); 48 | }); 49 | }); 50 | 51 | describe('errorName', function() { 52 | it('getErrorName should return string from error object', function() { 53 | expect(PrereqCheckError.getErrorName()).toEqual('notOp'); 54 | }); 55 | 56 | it('setErrorName should update error object', function() { 57 | var newErrorName = 'andOp'; 58 | 59 | PrereqCheckError.setErrorName(newErrorName); 60 | expect(PrereqCheckError.getErrorName()).toEqual(newErrorName); 61 | }); 62 | }); 63 | 64 | describe('errorLine', function() { 65 | it('getErrorLine should return integer from error object', function() { 66 | expect(PrereqCheckError.getErrorLineNumber()).toEqual( 67 | error.errorLineNumber); 68 | }); 69 | 70 | it('setErrorLine should update error object', function() { 71 | var newErrorLineNumber = 35; 72 | 73 | PrereqCheckError.setErrorLineNumber(newErrorLineNumber); 74 | expect(PrereqCheckError.getErrorLineNumber()).toEqual(newErrorLineNumber); 75 | }); 76 | }); 77 | 78 | describe('errorColumn', function() { 79 | it('getErrorLine should return integer from error object', function() { 80 | expect(PrereqCheckError.getErrorColumnNumber()).toEqual( 81 | error.errorColumnNumber); 82 | }); 83 | 84 | it('setErrorColumn should update error object', function() { 85 | var newErrorColumnNumber = 12; 86 | 87 | PrereqCheckError.setErrorColumnNumber(newErrorColumnNumber); 88 | expect(PrereqCheckError.getErrorColumnNumber()).toEqual( 89 | newErrorColumnNumber); 90 | }); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /client/question/domain/SnapshotObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for SnapshotObject domain objects. 17 | */ 18 | 19 | describe('SnapshotObjectFactory', function() { 20 | var SnapshotObjectFactory; 21 | var snapshot; 22 | var PrereqCheckFailureObjectFactory; 23 | var prereqCheckFailure; 24 | var CodeEvalResultObjectFactory; 25 | var codeEvalResult; 26 | var FeedbackObjectFactory; 27 | var feedback; 28 | var FEEDBACK_CATEGORIES; 29 | 30 | beforeEach(module('tie')); 31 | beforeEach(inject(function($injector) { 32 | FEEDBACK_CATEGORIES = $injector.get('FEEDBACK_CATEGORIES'); 33 | SnapshotObjectFactory = $injector.get('SnapshotObjectFactory'); 34 | snapshot = SnapshotObjectFactory.create({ 35 | codeEvalResult: null, 36 | feedback: null 37 | }); 38 | PrereqCheckFailureObjectFactory = $injector.get( 39 | 'PrereqCheckFailureObjectFactory'); 40 | prereqCheckFailure = PrereqCheckFailureObjectFactory.create( 41 | 'missingStarterCode', null, 'def myFunction(arg): return arg' 42 | ); 43 | CodeEvalResultObjectFactory = $injector.get( 44 | 'CodeEvalResultObjectFactory'); 45 | codeEvalResult = CodeEvalResultObjectFactory.create( 46 | 'code separator = "a"', 'code', '', 47 | [[true, true], [false, false]], [[false], [false]], [[], []], 48 | null, 'errorInput', false, false); 49 | FeedbackObjectFactory = $injector.get('FeedbackObjectFactory'); 50 | feedback = FeedbackObjectFactory.create(FEEDBACK_CATEGORIES.SUCCESSFUL); 51 | })); 52 | 53 | describe('setPrereqCheckFailure', function() { 54 | it('should correctly set and get prereqCheckFailure', function() { 55 | snapshot.setPrereqCheckFailure(prereqCheckFailure); 56 | expect(snapshot.getPrereqCheckFailure()).toEqual( 57 | PrereqCheckFailureObjectFactory.create( 58 | 'missingStarterCode', null, 'def myFunction(arg): return arg' 59 | )); 60 | }); 61 | }); 62 | 63 | describe('setCodeEvalResult', function() { 64 | it('should correctly set and get codeEvalResult', function() { 65 | snapshot.setCodeEvalResult(codeEvalResult); 66 | expect(snapshot.getCodeEvalResult()).toEqual( 67 | CodeEvalResultObjectFactory.create( 68 | 'code separator = "a"', 'code', '', 69 | [[true, true], [false, false]], [[false], [false]], 70 | [[], []], null, 'errorInput', false, false) 71 | ); 72 | }); 73 | }); 74 | 75 | describe('setFeedback', function() { 76 | it('should correctly set and get feedback', function() { 77 | snapshot.setFeedback(feedback); 78 | expect(snapshot.getFeedback()).toEqual( 79 | FeedbackObjectFactory.create(FEEDBACK_CATEGORIES.SUCCESSFUL)); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /client/question/domain/TracebackCoordinatesObjectFactory.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Factory for creating new frontend instances of 17 | * TracebackCoordinates domain objects. 18 | */ 19 | 20 | tie.factory('TracebackCoordinatesObjectFactory', [ 21 | function() { 22 | /** 23 | * TracebackCoordinates objects represent the coordinates (line number, 24 | * column number) for a singular traceback for an error. Multiple of 25 | * these may be present for an ErrorTraceback object. 26 | */ 27 | 28 | /** 29 | * Constructor for TracebackCoordinates 30 | * 31 | * @param {number} lineNumber 32 | * @param {number} columnNumber 33 | * @constructor 34 | */ 35 | var TracebackCoordinates = function(lineNumber, columnNumber) { 36 | /** 37 | * A number that corresponds with the line number for this traceback's 38 | * coordinates. 39 | * Note: This is 1-indexed. 40 | * 41 | * @type {number} 42 | * @private 43 | */ 44 | this._lineNumber = lineNumber; 45 | 46 | /** 47 | * A number that corresponds with the column number for this traceback's 48 | * coordinates. 49 | * Note: This is 1=indexed. 50 | * 51 | * @type {number} 52 | * @private 53 | */ 54 | this._columnNumber = columnNumber; 55 | }; 56 | 57 | // Instance methods. 58 | /** 59 | * A getter for the _lineNumber property. 60 | * 61 | * @returns {number} 62 | */ 63 | TracebackCoordinates.prototype.getLineNumber = function() { 64 | return this._lineNumber; 65 | }; 66 | 67 | // Static class methods. 68 | /** 69 | * Returns a TracebackCoordinates object based on the lineNumber and 70 | * columnNumber passed in as params. 71 | * 72 | * @param {number} lineNumber 73 | * @param {number} columnNumber 74 | * @returns {TracebackCoordinates} 75 | */ 76 | TracebackCoordinates.create = function(lineNumber, columnNumber) { 77 | return new TracebackCoordinates(lineNumber, columnNumber); 78 | }; 79 | 80 | return TracebackCoordinates; 81 | } 82 | ]); 83 | -------------------------------------------------------------------------------- /client/question/domain/TranscriptObjectFactory.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Factory for creating a transcript of a student's TIE 17 | * session. 18 | */ 19 | 20 | tie.factory('TranscriptObjectFactory', [ 21 | function() { 22 | /** 23 | * Transcript objects are used to store the snapshots associated with a 24 | * task that can then be used to track the student's progress/status. 25 | */ 26 | 27 | /** 28 | * Constructor for Transcript. 29 | * 30 | * @constructor 31 | */ 32 | var Transcript = function() { 33 | /** 34 | * An array of snapshots that are attached to this Transcript. 35 | * 36 | * @type {Array} 37 | * @private 38 | */ 39 | this._snapshots = []; 40 | }; 41 | 42 | // Instance methods. 43 | /** 44 | * This function returns the Snapshot object that occurred most recently. 45 | * Since these snapshots are saved chronologically, this is always the 46 | * last snapshot in the array. 47 | * 48 | * @returns {Snapshot} 49 | */ 50 | Transcript.prototype.getMostRecentSnapshot = function() { 51 | if (this._snapshots.length === 0) { 52 | return null; 53 | } 54 | return this._snapshots[this._snapshots.length - 1]; 55 | }; 56 | 57 | /** 58 | * This function appends a snapshot to the _snapshots Array and subsequently 59 | * returns the length of the Array. 60 | * 61 | * @param {Snapshot} snapshot 62 | * @returns {Number} 63 | */ 64 | Transcript.prototype.recordSnapshot = function(snapshot) { 65 | this._snapshots.push(snapshot); 66 | return this._snapshots.length; 67 | }; 68 | 69 | // Static class methods. 70 | /** 71 | * Returns a new Transcript object. 72 | * 73 | * @returns {Transcript} 74 | */ 75 | Transcript.create = function() { 76 | return new Transcript(); 77 | }; 78 | 79 | return Transcript; 80 | } 81 | ]); 82 | -------------------------------------------------------------------------------- /client/question/domain/TranscriptObjectFactorySpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for Transcript domain objects. 17 | */ 18 | 19 | describe('TranscriptObjectFactory', function() { 20 | var TranscriptObjectFactory; 21 | 22 | beforeEach(module('tie')); 23 | beforeEach(inject(function($injector) { 24 | TranscriptObjectFactory = $injector.get('TranscriptObjectFactory'); 25 | })); 26 | 27 | describe('getMostRecentSnapshot', function() { 28 | it('should retrieve the last snapshot', function() { 29 | var transcript = TranscriptObjectFactory.create(); 30 | transcript.recordSnapshot(1); 31 | transcript.recordSnapshot(2); 32 | 33 | expect(transcript.getMostRecentSnapshot()).toBe(2); 34 | }); 35 | 36 | it('should return null if there are no snapshots', function() { 37 | var transcript = TranscriptObjectFactory.create(); 38 | 39 | expect(transcript.getMostRecentSnapshot()).toBe(null); 40 | }); 41 | }); 42 | 43 | describe('recordSnapshot', function() { 44 | it('should return the number of snapshots taken when recording', 45 | function() { 46 | var transcript = TranscriptObjectFactory.create(); 47 | 48 | expect(transcript.recordSnapshot(1)).toBe(1); 49 | expect(transcript.recordSnapshot(1)).toBe(2); 50 | } 51 | ); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /client/question/services/AutosaveService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview A service for handling the autosave flow. 17 | */ 18 | 19 | tie.factory('AutosaveService', [ 20 | 'CurrentQuestionService', 'LocalStorageService', 21 | 'LocalStorageKeyManagerService', 22 | function( 23 | CurrentQuestionService, LocalStorageService, 24 | LocalStorageKeyManagerService) { 25 | return { 26 | /** 27 | * Retrieves the last-saved code from localStorage. 28 | * 29 | * @param {string} language The language that the code is written in. 30 | * @returns {string|null} The last saved code, or null if it does not 31 | * exist. 32 | */ 33 | getLastSavedCode: function(language) { 34 | if (!CurrentQuestionService.isInitialized()) { 35 | throw Error( 36 | 'CurrentQuestionService must be initialized before ' + 37 | 'AutosaveService.getLastSavedCode() is called.'); 38 | } 39 | 40 | var questionId = CurrentQuestionService.getCurrentQuestionId(); 41 | var localStorageKey = ( 42 | LocalStorageKeyManagerService.getLastSavedCodeKey( 43 | questionId, language)); 44 | return LocalStorageService.get(localStorageKey); 45 | }, 46 | 47 | /** 48 | * Saves the current version of the code in localStorage. 49 | * 50 | * @param {string} language The language that the code is written in. 51 | * @param {string} code The code to be saved. 52 | * @returns {string|null} The last saved code, or null if it does not 53 | * exist. 54 | */ 55 | saveCode: function(language, code) { 56 | var questionId = CurrentQuestionService.getCurrentQuestionId(); 57 | var localStorageKey = ( 58 | LocalStorageKeyManagerService.getLastSavedCodeKey( 59 | questionId, language)); 60 | LocalStorageService.put(localStorageKey, code); 61 | } 62 | }; 63 | } 64 | ]); 65 | -------------------------------------------------------------------------------- /client/question/services/AutosaveServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for the AutosaveService. 17 | */ 18 | 19 | describe('AutosaveService', function() { 20 | var AutosaveService; 21 | var CurrentQuestionService; 22 | // In the Karma test environment, the deferred promise gets resolved only 23 | // when $rootScope.$digest() is called. 24 | var $rootScope; 25 | 26 | beforeEach(module('tie')); 27 | beforeEach(inject(function($injector) { 28 | AutosaveService = $injector.get('AutosaveService'); 29 | CurrentQuestionService = $injector.get('CurrentQuestionService'); 30 | $rootScope = $injector.get('$rootScope'); 31 | localStorage.clear(); 32 | })); 33 | 34 | describe('basic functionality', function() { 35 | it('should only activate after the question is loaded', function(done) { 36 | expect(function() { 37 | AutosaveService.getLastSavedCode('python'); 38 | }).toThrow(); 39 | CurrentQuestionService.init(function() { 40 | expect(AutosaveService.getLastSavedCode('python')).toEqual(null); 41 | done(); 42 | }); 43 | $rootScope.$digest(); 44 | }); 45 | 46 | it('should save and retrieve code', function(done) { 47 | CurrentQuestionService.init(function() { 48 | AutosaveService.saveCode('python', 'py_code'); 49 | AutosaveService.saveCode('java', 'java_code'); 50 | expect(AutosaveService.getLastSavedCode('python')).toEqual('py_code'); 51 | expect(AutosaveService.getLastSavedCode('java')).toEqual('java_code'); 52 | done(); 53 | }); 54 | $rootScope.$digest(); 55 | }); 56 | 57 | it('should return null if code is not found', function(done) { 58 | CurrentQuestionService.init(function() { 59 | expect(AutosaveService.getLastSavedCode('python')).toEqual(null); 60 | done(); 61 | }); 62 | $rootScope.$digest(); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /client/question/services/CurrentQuestionService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview A service that maintains details of the currently-active 17 | * question in the learner view. 18 | */ 19 | 20 | tie.factory('CurrentQuestionService', [ 21 | '$location', 'DEFAULT_QUESTION_ID', 'SERVER_URL', 'QuestionDataService', 22 | function($location, DEFAULT_QUESTION_ID, SERVER_URL, QuestionDataService) { 23 | var questionId = ($location.search().qid || DEFAULT_QUESTION_ID); 24 | var questionVersion = null; 25 | var cachedQuestion = null; 26 | var serviceIsInitialized = false; 27 | 28 | return { 29 | init: function(callbackFunction) { 30 | var that = this; 31 | 32 | var questionPromise = QuestionDataService.fetchQuestionAsync( 33 | questionId); 34 | questionPromise.then(function(question) { 35 | if (question) { 36 | cachedQuestion = question; 37 | questionVersion = question.getVersion(); 38 | serviceIsInitialized = true; 39 | callbackFunction(); 40 | } else if (questionId === DEFAULT_QUESTION_ID) { 41 | // This is considered to be the failure case, but it should 42 | // call through; for instance, if extra setup / redirecting 43 | // is done after this method. 44 | callbackFunction(); 45 | } else { 46 | // If the question ID in the URL is invalid, revert to using the 47 | // default question ID. 48 | questionId = DEFAULT_QUESTION_ID; 49 | serviceIsInitialized = true; 50 | that.init(callbackFunction); 51 | } 52 | }); 53 | }, 54 | isInitialized: function() { 55 | return serviceIsInitialized; 56 | }, 57 | getCurrentQuestionId: function() { 58 | return questionId; 59 | }, 60 | getCurrentQuestionVersion: function() { 61 | return questionVersion; 62 | }, 63 | getCurrentQuestion: function() { 64 | return cachedQuestion; 65 | } 66 | }; 67 | } 68 | ]); 69 | -------------------------------------------------------------------------------- /client/question/services/LocalStorageKeyManagerService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview A service for managing the keys used in localStorage. All 17 | * localStorage key management should be done here in order to reduce the 18 | * likelihood of collisions. 19 | */ 20 | 21 | tie.factory('LocalStorageKeyManagerService', [ 22 | function() { 23 | // TIE uses local storage with the following key structure: 24 | // 25 | // - tie:1:lastSavedCode:{{questionId}}:{{language}} -- the last saved 26 | // code (a string) 27 | // - tie:1:sessionHistory:{{questionId}} -- the session history 28 | // (a list of speech balloon dicts) 29 | // 30 | // The second number in each key is for version control. If this schema 31 | // changes, that number should be updated to prevent collision. 32 | 33 | return { 34 | /** 35 | * Returns the local storage key for the last saved code for a given 36 | * question. 37 | * 38 | * @param {string} questionId 39 | * @param {string} language 40 | * @returns {string} 41 | */ 42 | getLastSavedCodeKey: function(questionId, language) { 43 | return 'tie:1:lastSavedCode:' + questionId + ':' + language; 44 | }, 45 | 46 | /** 47 | * Returns the local storage key for the session history. 48 | * 49 | * @param {string} questionId 50 | * @returns {string} 51 | */ 52 | getSessionHistoryKey: function(questionId) { 53 | return 'tie:1:sessionHistory:' + questionId; 54 | } 55 | }; 56 | } 57 | ]); 58 | -------------------------------------------------------------------------------- /client/question/services/LocalStorageKeyManagerServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for the LocalStorageKeyManagerService. 17 | */ 18 | 19 | describe('LocalStorageKeyManagerService', function() { 20 | var LocalStorageKeyManagerService; 21 | 22 | beforeEach(module('tie')); 23 | beforeEach(inject(function($injector) { 24 | LocalStorageKeyManagerService = $injector.get( 25 | 'LocalStorageKeyManagerService'); 26 | })); 27 | 28 | describe('last-saved code key generation', function() { 29 | it('should correctly generate last-saved code key', function() { 30 | expect(LocalStorageKeyManagerService.getLastSavedCodeKey( 31 | 'qid', 'python')).toBe('tie:1:lastSavedCode:qid:python'); 32 | expect(LocalStorageKeyManagerService.getLastSavedCodeKey( 33 | 'qid2', 'python')).toBe('tie:1:lastSavedCode:qid2:python'); 34 | }); 35 | }); 36 | 37 | describe('session history key generation', function() { 38 | it('should correctly generate session history key', function() { 39 | expect(LocalStorageKeyManagerService.getSessionHistoryKey( 40 | 'qid')).toBe('tie:1:sessionHistory:qid'); 41 | expect(LocalStorageKeyManagerService.getSessionHistoryKey( 42 | 'qid2')).toBe('tie:1:sessionHistory:qid2'); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /client/question/services/LocalStorageService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview A service that wraps the browser's localStorage object. Note 17 | * that localStorage namespacing should be handled by 18 | * LocalStorageKeyManagerService instead. 19 | * 20 | * All values that TIE stores in localStorage are encoded as JSON strings. 21 | */ 22 | 23 | tie.factory('LocalStorageService', [ 24 | 'ServerHandlerService', function(ServerHandlerService) { 25 | var localStorageIsAvailable = false; 26 | // We only use localStorage in the standalone version of the application. 27 | if (!ServerHandlerService.doesServerExist()) { 28 | // In some browsers, localStorage is not available and its invocation 29 | // throws an error. 30 | try { 31 | localStorageIsAvailable = Boolean(localStorage); 32 | } catch (e) { 33 | localStorageIsAvailable = false; 34 | } 35 | } 36 | 37 | return { 38 | /** 39 | * Checks if the local storage is available. 40 | * 41 | * @returns {boolean} 42 | */ 43 | isAvailable: function() { 44 | return localStorageIsAvailable; 45 | }, 46 | 47 | /** 48 | * Deletes an item in local storage. 49 | * 50 | * @param {string} localStorageKey 51 | * 52 | * @returns {*} The data corresponding to the key, or null if the item 53 | * does not exist in local storage. 54 | */ 55 | get: function(localStorageKey) { 56 | if (!localStorageIsAvailable) { 57 | return null; 58 | } 59 | 60 | return angular.fromJson(localStorage.getItem(localStorageKey)); 61 | }, 62 | 63 | /** 64 | * Saves an item to local storage. 65 | * 66 | * @param {string} localStorageKey 67 | * @param {*} value The value to associate with the key. This value can 68 | * be any standard JavaScript construct that is losslessly 69 | * JSON-serializable. 70 | */ 71 | put: function(localStorageKey, value) { 72 | if (!localStorageIsAvailable) { 73 | return; 74 | } 75 | 76 | localStorage.setItem(localStorageKey, angular.toJson(value)); 77 | }, 78 | 79 | /** 80 | * Deletes an item in local storage. 81 | * 82 | * @param {string} localStorageKey 83 | */ 84 | delete: function(localStorageKey) { 85 | if (!localStorageIsAvailable) { 86 | return; 87 | } 88 | 89 | localStorage.removeItem(localStorageKey); 90 | } 91 | }; 92 | } 93 | ]); 94 | -------------------------------------------------------------------------------- /client/question/services/LocalStorageServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for the LocalStorageService. 17 | * Please be aware, the hash key format is {{questionId}}:{{language}} 18 | */ 19 | 20 | describe('LocalStorageService', function() { 21 | var LocalStorageService; 22 | 23 | beforeEach(module('tie')); 24 | afterEach(function() { 25 | localStorage.clear(); 26 | }); 27 | 28 | describe('LocalStorageService', function() { 29 | describe('with a server', function() { 30 | beforeEach(module(function($provide) { 31 | $provide.value('ServerHandlerService', { 32 | doesServerExist: function() { 33 | return true; 34 | } 35 | }); 36 | })); 37 | 38 | beforeEach(inject(function($injector) { 39 | LocalStorageService = $injector.get('LocalStorageService'); 40 | localStorage.clear(); 41 | })); 42 | 43 | it('should be unavailable', function() { 44 | expect(LocalStorageService.isAvailable()).toBe(false); 45 | }); 46 | 47 | it('should not save or retrieve anything', function() { 48 | LocalStorageService.put('abc', 'def'); 49 | expect(localStorage.getItem('abc')).toBe(null); 50 | expect(LocalStorageService.get('abc')).toBe(null); 51 | }); 52 | }); 53 | 54 | describe('with no server', function() { 55 | beforeEach(module(function($provide) { 56 | $provide.value('ServerHandlerService', { 57 | doesServerExist: function() { 58 | return false; 59 | } 60 | }); 61 | })); 62 | 63 | beforeEach(inject(function($injector) { 64 | LocalStorageService = $injector.get('LocalStorageService'); 65 | localStorage.clear(); 66 | })); 67 | 68 | it('should be available', function() { 69 | expect(LocalStorageService.isAvailable()).toBe(true); 70 | }); 71 | 72 | it('should save and retrieve correctly', function() { 73 | LocalStorageService.put('abc', 'value'); 74 | expect(LocalStorageService.get('abc')).toBe('value'); 75 | LocalStorageService.put('abc', 'newValue'); 76 | expect(LocalStorageService.get('abc')).toBe('newValue'); 77 | }); 78 | 79 | it('should delete values correctly', function() { 80 | LocalStorageService.put('abc', 'value'); 81 | expect(LocalStorageService.get('abc')).toBe('value'); 82 | LocalStorageService.delete('abc'); 83 | expect(LocalStorageService.get('abc')).toBe(null); 84 | }); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /client/question/services/MonospaceDisplayModalService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | /** 15 | * @fileoverview Service for maintaining the state of the monospace display 16 | * modal. 17 | */ 18 | 19 | tie.factory('MonospaceDisplayModalService', [ 20 | function() { 21 | // These are null if and only if the modal is hidden. 22 | var currentContentLines = null; 23 | var currentTitle = null; 24 | 25 | var onModalOpenCallbacks = []; 26 | 27 | return { 28 | /** 29 | * Retrieves the title to be displayed. 30 | */ 31 | getTitle: function() { 32 | return currentTitle; 33 | }, 34 | /** 35 | * Retrieves the content to be displayed. 36 | */ 37 | getContentLines: function() { 38 | return currentContentLines; 39 | }, 40 | /** 41 | * Returns whether the modal is displayed or not. 42 | */ 43 | isDisplayed: function() { 44 | return (currentContentLines !== null); 45 | }, 46 | /** 47 | * Adds a new observer callback, to be called when the modal is opened. 48 | * 49 | * @param {function} The callback to register. 50 | */ 51 | registerCallback: function(newCallback) { 52 | onModalOpenCallbacks.push(newCallback); 53 | }, 54 | /** 55 | * Displays the modal, using the given title and content. 56 | * 57 | * @param {string} newTitle The title of the modal. 58 | * @param {Array} newContentLines An array with the lines of 59 | * content to display. 60 | */ 61 | showModal: function(newTitle, newContentLines) { 62 | currentTitle = newTitle; 63 | currentContentLines = newContentLines; 64 | onModalOpenCallbacks.forEach(function(onModalOpenCallback) { 65 | onModalOpenCallback(); 66 | }); 67 | }, 68 | /** 69 | * Hides the modal. 70 | */ 71 | hideModal: function() { 72 | currentContentLines = null; 73 | currentTitle = null; 74 | } 75 | }; 76 | } 77 | ]); 78 | -------------------------------------------------------------------------------- /client/question/services/MonospaceDisplayModalServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for the MonospaceDisplayModalService. 17 | */ 18 | 19 | describe('MonospaceDisplayModalServiceSpec', function() { 20 | var MonospaceDisplayModalService; 21 | 22 | beforeEach(module('tie')); 23 | beforeEach(inject(function($injector) { 24 | MonospaceDisplayModalService = $injector.get( 25 | 'MonospaceDisplayModalService'); 26 | })); 27 | 28 | describe('hidden and shown states', function() { 29 | it('should correctly hide the modal', function() { 30 | MonospaceDisplayModalService.hideModal(); 31 | expect(MonospaceDisplayModalService.getTitle()).toBe(null); 32 | expect(MonospaceDisplayModalService.getContentLines()).toBe(null); 33 | expect(MonospaceDisplayModalService.isDisplayed()).toBe(false); 34 | }); 35 | 36 | it('should correctly show the modal', function() { 37 | MonospaceDisplayModalService.showModal('new title', ['content 1']); 38 | expect(MonospaceDisplayModalService.getTitle()).toBe('new title'); 39 | expect(MonospaceDisplayModalService.getContentLines()) 40 | .toEqual(['content 1']); 41 | expect(MonospaceDisplayModalService.isDisplayed()).toBe(true); 42 | }); 43 | 44 | it('should correctly replace the modal contents', function() { 45 | MonospaceDisplayModalService.showModal('new title', ['content 1']); 46 | MonospaceDisplayModalService.showModal('newer title', ['content 2']); 47 | expect(MonospaceDisplayModalService.getTitle()).toBe('newer title'); 48 | expect(MonospaceDisplayModalService.getContentLines()) 49 | .toEqual(['content 2']); 50 | expect(MonospaceDisplayModalService.isDisplayed()).toBe(true); 51 | }); 52 | }); 53 | 54 | describe('on-load callbacks', function() { 55 | it('should correctly register a callback', function() { 56 | var testData = { 57 | sampleCallback: function() { 58 | return 0; 59 | } 60 | }; 61 | spyOn(testData, 'sampleCallback'); 62 | 63 | MonospaceDisplayModalService.registerCallback(testData.sampleCallback); 64 | MonospaceDisplayModalService.showModal('title', ['content']); 65 | expect(testData.sampleCallback).toHaveBeenCalled(); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /client/question/services/ParentPageService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Service that handles all TIE interactions with its parent 17 | * page. 18 | */ 19 | tie.factory('ParentPageService', [ 20 | '$window', 'EXPECTED_PARENT_PAGE_ORIGIN', 21 | function($window, EXPECTED_PARENT_PAGE_ORIGIN) { 22 | /** 23 | * Used to define the raw code message type. 24 | * @type {string} 25 | * @constant 26 | */ 27 | var MESSAGE_TYPE_RAW_CODE = 'raw_code'; 28 | 29 | /** 30 | * Sends the message to the parent page, if it exists and matches what is 31 | * expected, via postMessage. 32 | * 33 | * @param {string} messageType Type of message that is being sent. 34 | * @param {string} messageData Data to be sent to parent page. 35 | */ 36 | var sendMessage = function(messageType, messageData) { 37 | if (EXPECTED_PARENT_PAGE_ORIGIN !== null && 38 | isSameOriginForParentPageAndFrame()) { 39 | $window.parent.postMessage( 40 | JSON.stringify(messageData), EXPECTED_PARENT_PAGE_ORIGIN); 41 | } 42 | }; 43 | 44 | /** 45 | * Checks whether the parent window has the same origin as 46 | * specified in config. 47 | * 48 | * @returns {boolean} 49 | */ 50 | var isSameOriginForParentPageAndFrame = function() { 51 | /** 52 | * 0-indexed element in the array refers to the most immediate parent 53 | * frame origin. 54 | */ 55 | var parentFrameOrigin = $window.location.ancestorOrigins[0]; 56 | return parentFrameOrigin === EXPECTED_PARENT_PAGE_ORIGIN; 57 | }; 58 | 59 | return { 60 | /** 61 | * Sends the raw user code to the parent page, if it exists and matches 62 | * the expected parent origin. 63 | * 64 | * @param {string} rawCode User code to send to parent page. 65 | */ 66 | sendRawCode: function(rawCode) { 67 | sendMessage(MESSAGE_TYPE_RAW_CODE, rawCode); 68 | }, 69 | 70 | /** 71 | * Returns whether TIE is being iframed by a page that has the same 72 | * origin as specified in config. 73 | * 74 | * @returns {boolean} 75 | */ 76 | isIframed: function() { 77 | return EXPECTED_PARENT_PAGE_ORIGIN !== null && 78 | isSameOriginForParentPageAndFrame(); 79 | } 80 | }; 81 | } 82 | ]); 83 | -------------------------------------------------------------------------------- /client/question/services/ParentPageServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for ParentPageService. 17 | */ 18 | 19 | describe('ParentPageService', function() { 20 | var ParentPageService; 21 | 22 | beforeEach(module('tie')); 23 | var setParentPageOrigin = function(parentPageOrigin) { 24 | module('tieConfig', function($provide) { 25 | $provide.constant('EXPECTED_PARENT_PAGE_ORIGIN', parentPageOrigin); 26 | }); 27 | }; 28 | 29 | var parentPageObject = { 30 | // Execution of this method signifies that the window received an event. 31 | receiveMessage: function() { 32 | // eslint-disable-next-line no-useless-return 33 | return; 34 | } 35 | }; 36 | 37 | describe('sendRawCode', function() { 38 | describe('when there is no parent page', function() { 39 | beforeEach(function() { 40 | spyOn(parentPageObject, 'receiveMessage'); 41 | setParentPageOrigin(null); 42 | inject(function($injector) { 43 | ParentPageService = $injector.get('ParentPageService'); 44 | }); 45 | ParentPageService.sendRawCode('code to be sent'); 46 | }); 47 | 48 | it('should not receive any messages', function() { 49 | expect(parentPageObject.receiveMessage).not.toHaveBeenCalled(); 50 | }); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /client/question/services/PrintTerminalService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Service that provides a way to run different methods on the 17 | * print-enabled and print-disabled versions of TIE. 18 | */ 19 | tie.factory('PrintTerminalService', [ 20 | 'ALLOW_PRINTING', function(ALLOW_PRINTING) { 21 | return { 22 | /** 23 | * Returns whether or not printing to stdout is supported. 24 | * 25 | * @return {boolean} True if printing is supported, false if not. 26 | */ 27 | isPrintingSupported: function() { 28 | return ALLOW_PRINTING; 29 | } 30 | }; 31 | } 32 | ]); 33 | -------------------------------------------------------------------------------- /client/question/services/PrintTerminalServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for PrintTerminalService. 17 | */ 18 | 19 | describe('PrintTerminalService', function() { 20 | var PrintTerminalService; 21 | 22 | beforeEach(module('tie')); 23 | var setPrintingConfig = function(isSupported) { 24 | module('tieConfig', function($provide) { 25 | $provide.constant('ALLOW_PRINTING', isSupported); 26 | }); 27 | }; 28 | 29 | describe("isPrintingSupported", function() { 30 | it('should return true if printing is supported', function() { 31 | setPrintingConfig(true); 32 | inject(function($injector) { 33 | PrintTerminalService = $injector.get('PrintTerminalService'); 34 | }); 35 | expect(PrintTerminalService.isPrintingSupported()).toEqual(true); 36 | }); 37 | 38 | it('should return false if printing is not supported', function() { 39 | setPrintingConfig(false); 40 | inject(function($injector) { 41 | PrintTerminalService = $injector.get('PrintTerminalService'); 42 | }); 43 | expect(PrintTerminalService.isPrintingSupported()).toEqual(false); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /client/question/services/ServerHandlerService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Service that provides a way to run different methods on the 17 | * server- and client-only versions of TIE. 18 | */ 19 | tie.factory('ServerHandlerService', ['SERVER_URL', function(SERVER_URL) { 20 | return { 21 | /** 22 | * Returns whether or not we're using the server version of TIE. 23 | * 24 | * @return {boolean} True if we should use the server, false otherwise. 25 | */ 26 | doesServerExist: function() { 27 | return SERVER_URL !== null; 28 | } 29 | }; 30 | }]); 31 | -------------------------------------------------------------------------------- /client/question/services/ServerHandlerServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for ServerHandlerService. 17 | */ 18 | 19 | describe('ServerHandlerService', function() { 20 | var ServerHandlerService; 21 | 22 | beforeEach(module('tie')); 23 | var setServerUrl = function(url) { 24 | module('tieConfig', function($provide) { 25 | $provide.constant('SERVER_URL', url); 26 | }); 27 | }; 28 | 29 | describe("doesServerExist", function() { 30 | it('should return true if SERVER_URL exists', function() { 31 | setServerUrl('http://katamari.com'); 32 | inject(function($injector) { 33 | ServerHandlerService = $injector.get('ServerHandlerService'); 34 | }); 35 | expect(ServerHandlerService.doesServerExist()).toEqual(true); 36 | 37 | }); 38 | 39 | it('should return false if SERVER_URL is null', function() { 40 | setServerUrl(null); 41 | inject(function($injector) { 42 | ServerHandlerService = $injector.get('ServerHandlerService'); 43 | }); 44 | expect(ServerHandlerService.doesServerExist()).toEqual(false); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /client/question/services/SessionIdService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Service that maintains an anonymized record of the user's 17 | * session via a unique session ID. 18 | */ 19 | 20 | tie.factory('SessionIdService', [ 21 | function() { 22 | var SESSION_ID_LENGTH = 100; 23 | 24 | /** 25 | * Global object to keep track of the session ID. 26 | * @type {string} 27 | */ 28 | var sessionId = null; 29 | 30 | /** 31 | * Generates a pseudorandom sessionId. 32 | * 33 | * @returns {string} 34 | */ 35 | var _createSessionId = function() { 36 | var idArray = []; 37 | var possible = ['ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 38 | '0123456789'].join(''); 39 | 40 | for (var i = 0; i < SESSION_ID_LENGTH; i++) { 41 | idArray.push( 42 | possible.charAt(Math.floor(Math.random() * possible.length))); 43 | } 44 | return idArray.join(''); 45 | }; 46 | 47 | return { 48 | _createSessionId: _createSessionId, 49 | 50 | /** 51 | * A getter for the sessionId property. Will also create a sessionId 52 | * if the current sessionId is null. 53 | * 54 | * @returns {string} 55 | */ 56 | getSessionId: function() { 57 | if (sessionId === null) { 58 | sessionId = _createSessionId(); 59 | } 60 | return sessionId; 61 | }, 62 | 63 | /** 64 | * A method that resets the sessionId property. 65 | */ 66 | resetSessionId: function() { 67 | sessionId = _createSessionId(); 68 | } 69 | }; 70 | } 71 | ]); 72 | -------------------------------------------------------------------------------- /client/question/services/SessionIdServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for SessionIdService. 17 | */ 18 | 19 | describe('SessionIdService', function() { 20 | var SessionIdService; 21 | 22 | beforeEach(module('tie')); 23 | beforeEach(inject(function($injector) { 24 | SessionIdService = $injector.get( 25 | 'SessionIdService'); 26 | })); 27 | 28 | describe('getSessionId', function() { 29 | it('should never return null', function(done) { 30 | var id = SessionIdService.getSessionId(); 31 | expect(id).not.toEqual(null); 32 | done(); 33 | }); 34 | 35 | it('should return the same sessionId across multiple calls', 36 | function(done) { 37 | var id = SessionIdService.getSessionId(); 38 | var otherId = SessionIdService.getSessionId(); 39 | expect(id).toEqual(otherId); 40 | done(); 41 | }); 42 | 43 | it('should reset the sessionId', function(done) { 44 | var id = SessionIdService.getSessionId(); 45 | SessionIdService.resetSessionId(); 46 | var otherId = SessionIdService.getSessionId(); 47 | expect(id).not.toEqual(otherId); 48 | done(); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /client/question/services/ThemeNameService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | /** 15 | * @fileoverview A service that stores the user's light/dark mode preference. 16 | */ 17 | 18 | tie.constant('THEME_NAME_LIGHT', 'Light Theme'); 19 | tie.constant('THEME_NAME_DARK', 'Dark Theme'); 20 | 21 | tie.factory('ThemeNameService', [ 22 | 'PRIMER_DIRECTORY_URL', 'THEME_NAME_LIGHT', 'THEME_NAME_DARK', 23 | function(PRIMER_DIRECTORY_URL, THEME_NAME_LIGHT, THEME_NAME_DARK) { 24 | var currentThemeName = THEME_NAME_LIGHT; 25 | 26 | return { 27 | setThemeName: function(newThemeName) { 28 | currentThemeName = newThemeName; 29 | }, 30 | getCurrentThemeName: function() { 31 | return currentThemeName; 32 | }, 33 | isDarkModeEnabled: function() { 34 | return currentThemeName === THEME_NAME_DARK; 35 | }, 36 | /** 37 | * Provides the URL to the appropriately themed python primer file. 38 | */ 39 | getPythonPrimerUrl: function() { 40 | var primerTheme = this.isDarkModeEnabled() ? 'dark' : 'light'; 41 | return ( 42 | PRIMER_DIRECTORY_URL + 'py-primer-' + primerTheme + '.html'); 43 | } 44 | }; 45 | } 46 | ]); 47 | -------------------------------------------------------------------------------- /client/question/services/ThemeNameServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for the ThemeNameService. 17 | */ 18 | 19 | describe('ThemeNameService', function() { 20 | var ThemeNameService; 21 | var PRIMER_DIRECTORY_URL; 22 | var THEME_NAME_LIGHT; 23 | var THEME_NAME_DARK; 24 | 25 | beforeEach(module('tie')); 26 | beforeEach(inject(function($injector) { 27 | ThemeNameService = $injector.get('ThemeNameService'); 28 | PRIMER_DIRECTORY_URL = $injector.get('PRIMER_DIRECTORY_URL'); 29 | THEME_NAME_LIGHT = $injector.get('THEME_NAME_LIGHT'); 30 | THEME_NAME_DARK = $injector.get('THEME_NAME_DARK'); 31 | })); 32 | 33 | describe('setThemeName', function() { 34 | it('should correctly set and retrieve the theme name', function() { 35 | ThemeNameService.setThemeName(THEME_NAME_LIGHT); 36 | expect(ThemeNameService.getCurrentThemeName()).toEqual(THEME_NAME_LIGHT); 37 | 38 | ThemeNameService.setThemeName(THEME_NAME_DARK); 39 | expect(ThemeNameService.getCurrentThemeName()).toEqual(THEME_NAME_DARK); 40 | }); 41 | }); 42 | 43 | describe('isInDarkMode', function() { 44 | it('should correctly identify whether the theme is dark mode', function() { 45 | ThemeNameService.setThemeName(THEME_NAME_LIGHT); 46 | expect(ThemeNameService.isDarkModeEnabled()).toBe(false); 47 | 48 | ThemeNameService.setThemeName(THEME_NAME_DARK); 49 | expect(ThemeNameService.isDarkModeEnabled()).toBe(true); 50 | }); 51 | }); 52 | 53 | describe('getPythonPrimerUrl', function() { 54 | it('should correctly get the appropriate Python primer URL', function() { 55 | ThemeNameService.setThemeName(THEME_NAME_LIGHT); 56 | expect(ThemeNameService.getPythonPrimerUrl()).toBe( 57 | PRIMER_DIRECTORY_URL + 'py-primer-light.html'); 58 | 59 | ThemeNameService.setThemeName(THEME_NAME_DARK); 60 | expect(ThemeNameService.getPythonPrimerUrl()).toBe( 61 | PRIMER_DIRECTORY_URL + 'py-primer-dark.html'); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /client/question/services/code_evaluators/CodeRunnerDispatcherService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Dispatcher service that runs a user's submitted code using 17 | * the appropriate code evaluation engine. 18 | */ 19 | 20 | tie.factory('CodeRunnerDispatcherService', [ 21 | 'PythonCodeRunnerService', 'LANGUAGE_PYTHON', 22 | function(PythonCodeRunnerService, LANGUAGE_PYTHON) { 23 | return { 24 | /** 25 | * Asynchronously runs the code in the correct corresponding language 26 | * as passed in by the language parameter. 27 | * 28 | * @param {string} language 29 | * @param {PreprocessedCode} preprocessedCode 30 | * @returns {Promise} 31 | */ 32 | runCodeAsync: function(language, preprocessedCode) { 33 | if (language === LANGUAGE_PYTHON) { 34 | return PythonCodeRunnerService.runCodeAsync(preprocessedCode); 35 | } else { 36 | throw Error('Language not supported: ' + language); 37 | } 38 | }, 39 | /** 40 | * Asynchronously compiles the code in the correct corresponding language 41 | * as passed in by the language parameter. 42 | * 43 | * @param {string} language 44 | * @param {string} code 45 | * @returns {Promise} 46 | */ 47 | compileCodeAsync: function(language, code) { 48 | if (language === LANGUAGE_PYTHON) { 49 | return PythonCodeRunnerService.compileCodeAsync(code); 50 | } else { 51 | throw Error('Language not supported: ' + language); 52 | } 53 | } 54 | }; 55 | } 56 | ]); 57 | -------------------------------------------------------------------------------- /client/question/services/code_evaluators/CodeRunnerDispatcherServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for CodeRunnerDispatcherService 17 | */ 18 | 19 | describe('CodeRunnerDispatcherService', function() { 20 | var CodeRunnerDispatcherService; 21 | var PreprocessedCodeObjectFactory; 22 | 23 | beforeEach(module('tie')); 24 | beforeEach(inject(function($injector) { 25 | CodeRunnerDispatcherService = $injector.get('CodeRunnerDispatcherService'); 26 | PreprocessedCodeObjectFactory = $injector.get( 27 | 'PreprocessedCodeObjectFactory'); 28 | })); 29 | 30 | describe('pythonCodeRunnerService', function() { 31 | it('should throw an error if passed a non-Python language', function() { 32 | var code = 'some code'; 33 | var preprocessedCodeObject = PreprocessedCodeObjectFactory.create( 34 | code, code, 'separator'); 35 | 36 | var errorFunction = function() { 37 | CodeRunnerDispatcherService.runCodeAsync('java', 38 | preprocessedCodeObject); 39 | }; 40 | expect(errorFunction).toThrow(); 41 | }); 42 | 43 | it('should throw TimeLimitError given infinite-loop code', function(done) { 44 | var code = [ 45 | 'while True:', 46 | " print 'a'" 47 | ].join('\n'); 48 | var preprocessedCodeObject = PreprocessedCodeObjectFactory.create( 49 | code, code, 'separator'); 50 | CodeRunnerDispatcherService.runCodeAsync('python', 51 | preprocessedCodeObject).then( 52 | function(rawCodeEvalResult) { 53 | expect(rawCodeEvalResult.getErrorString()).toBe( 54 | 'TimeLimitError: Program exceeded run time limit.'); 55 | done(); 56 | } 57 | ); 58 | }); 59 | 60 | it('should throw RangeError given infinite-recurse code', function(done) { 61 | var code = [ 62 | 'def forgetToIgnoreSpaces(string):', 63 | " return forgetToIgnoreSpaces(string + 'b')", 64 | "forgetToIgnoreSpaces('a')" 65 | ].join('\n'); 66 | var preprocessedCodeObject = PreprocessedCodeObjectFactory.create( 67 | code, code, 'separator'); 68 | CodeRunnerDispatcherService.runCodeAsync("python", 69 | preprocessedCodeObject).then( 70 | function(rawCodeEvalResult) { 71 | expect(rawCodeEvalResult.getErrorString()).toBe( 72 | 'ExternalError: RangeError: Maximum call stack size exceeded'); 73 | done(); 74 | } 75 | ); 76 | }); 77 | }); 78 | 79 | describe('compileCodeAsync', function() { 80 | it('should throw an error if passed a non-Python language', function() { 81 | var errorFunction = function() { 82 | CodeRunnerDispatcherService.compileCodeAsync('java', 'some code'); 83 | }; 84 | expect(errorFunction).toThrow(); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /client/question/services/code_evaluators/PrereqCheckDispatcherService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Dispatcher service that performs pre-requisite checks on the 17 | * user's submitted code using the appropriate code evaluation engine. 18 | */ 19 | 20 | tie.factory('PrereqCheckDispatcherService', [ 21 | 'PythonPrereqCheckService', 'LANGUAGE_PYTHON', 22 | function(PythonPrereqCheckService, LANGUAGE_PYTHON) { 23 | return { 24 | /** 25 | * Calls the correct prereq check service for the language passed into 26 | * the function through the language parameter. 27 | * 28 | * @param {string} language 29 | * @param {string} starterCode 30 | * @param {string} code 31 | * @returns {PrereqCheckFailure} 32 | */ 33 | // Returns a PrereqCheckFailure object (or null if there are no failures). 34 | checkCode: function(language, starterCode, code) { 35 | if (language === LANGUAGE_PYTHON) { 36 | return PythonPrereqCheckService.checkCode(starterCode, code); 37 | } else { 38 | throw Error('Language not supported: ' + language); 39 | } 40 | }, 41 | /** 42 | * Returns a list of lines that do not contain strings, in order to 43 | * prevent spurious false positives in code-analysis checks. 44 | * 45 | * @param {string} language The language in which the code is written. 46 | * @param {string} code The code to analyze. 47 | * @returns {Array} A list of lines of the original code. Lines 48 | * containing strings are omitted. 49 | */ 50 | getNonStringLines: function(language, code) { 51 | if (language === LANGUAGE_PYTHON) { 52 | return PythonPrereqCheckService.getNonStringLines(code); 53 | } else { 54 | throw Error('Language not supported: ' + language); 55 | } 56 | } 57 | }; 58 | } 59 | ]); 60 | -------------------------------------------------------------------------------- /client/question/services/code_evaluators/PrereqCheckDispatcherServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for the PrereqCheckDispatcherServiceSpec. 17 | */ 18 | describe('PrereqCheckDispatcherService', function() { 19 | var PrereqCheckDispatcherService; 20 | var LANGUAGE_PYTHON = 'python'; 21 | 22 | beforeEach(module('tie')); 23 | beforeEach(inject(function($injector) { 24 | PrereqCheckDispatcherService = $injector.get( 25 | 'PrereqCheckDispatcherService'); 26 | })); 27 | 28 | describe('checkCode', function() { 29 | it('correctly calls PythonPrereqCheckService when the language is python', 30 | function() { 31 | var code = [ 32 | 'def myFunction(arg):', 33 | ' return result', 34 | 'def yourFunction(arg):', 35 | ' return result', 36 | '' 37 | ].join('\n'); 38 | var prereqCheckResults = PrereqCheckDispatcherService.checkCode( 39 | LANGUAGE_PYTHON, code, code); 40 | expect(prereqCheckResults).toEqual(null); 41 | } 42 | ); 43 | 44 | it('throws an error when an unsupported language is used', function() { 45 | expect(function() { 46 | PrereqCheckDispatcherService.checkCode('invalidLanguage', '', ''); 47 | }).toThrow(); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /client/question/services/code_preprocessors/CodePreprocessorDispatcherService.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Dispatcher service that preprocesses a user's submitted code 17 | * using the appropriate language-specific service. 18 | */ 19 | 20 | tie.factory('CodePreprocessorDispatcherService', [ 21 | 'PythonCodePreprocessorService', 'LANGUAGE_PYTHON', 22 | function(PythonCodePreprocessorService, LANGUAGE_PYTHON) { 23 | return { 24 | preprocess: function(language, codeSubmission, auxiliaryCode, tasks) { 25 | if (language === LANGUAGE_PYTHON) { 26 | return PythonCodePreprocessorService.preprocess( 27 | codeSubmission, auxiliaryCode, tasks); 28 | } else { 29 | throw Error('Language not supported: ' + language); 30 | } 31 | } 32 | }; 33 | } 34 | ]); 35 | -------------------------------------------------------------------------------- /client/question/services/code_preprocessors/CodePreprocessorDispatcherServiceSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Unit tests for the CodePreprocessorDispatcherService. 17 | */ 18 | 19 | describe('CodePreprocessorDispatcherService', function() { 20 | var CodePreprocessorDispatcherService; 21 | var LANGUAGE_PYTHON; 22 | var CodeSubmissionObjectFactory; 23 | var TaskObjectFactory; 24 | 25 | beforeEach(module('tie')); 26 | beforeEach(inject(function($injector) { 27 | CodePreprocessorDispatcherService = $injector.get( 28 | 'CodePreprocessorDispatcherService'); 29 | LANGUAGE_PYTHON = $injector.get('LANGUAGE_PYTHON'); 30 | CodeSubmissionObjectFactory = $injector.get('CodeSubmissionObjectFactory'); 31 | TaskObjectFactory = $injector.get('TaskObjectFactory'); 32 | })); 33 | 34 | describe('preprocess', function() { 35 | it('should throw error if the language passed in is unknown', function() { 36 | var unsupportedLanguage = 'someLanguage'; 37 | var failingFunction = function() { 38 | CodePreprocessorDispatcherService.preprocess( 39 | unsupportedLanguage, '', '', '', '', '', '', '', ''); 40 | }; 41 | expect(failingFunction).toThrow(new Error( 42 | 'Language not supported: someLanguage')); 43 | }); 44 | 45 | it('should preprocess python source code', function() { 46 | var codeSubmission = CodeSubmissionObjectFactory.create('a = 1\nb= 2'); 47 | var originProcessedCode = codeSubmission.getPreprocessedCode(); 48 | var task = TaskObjectFactory.create({ 49 | testSuites: [], 50 | buggyOutputTests: [], 51 | suiteLevelTests: [], 52 | performanceTests: [] 53 | }); 54 | CodePreprocessorDispatcherService.preprocess( 55 | LANGUAGE_PYTHON, codeSubmission, '', [task]); 56 | expect(codeSubmission.getPreprocessedCode()) 57 | .not.toEqual(originProcessedCode); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /hooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The TIE Authors. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS-IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Usage: 18 | # Not meant for manual use. 19 | # Runs automatically before a git push command is executed. 20 | # Behaviour can be set up using "bash scripts/setup.sh". 21 | 22 | check_test_status () { 23 | # Checking exit code returned. 24 | if [ $? -eq 0 ] 25 | then 26 | echo "$1 tests passed" 27 | else 28 | echo "$1 tests failed. Halting submission." 29 | exit 1 30 | fi 31 | } 32 | 33 | echo "Running linter tests" 34 | bash scripts/run_linter.sh 35 | check_test_status "Linter" 36 | 37 | echo "Running karma tests" 38 | bash scripts/run_karma_tests.sh 39 | check_test_status "Karma" 40 | 41 | exit 0 42 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for TIE. 2 | // Generated (and revised) on Mon Feb 13 2017 17:54:48 GMT-0800 (PST). 3 | 4 | module.exports = function(config) { 5 | // NOTE TO DEVELOPERS: to enable coverage reports, call `karma start` with 6 | // the added arg `--enable-coverage`. We disable it by default because it 7 | // interferes with the debugger during regular development. 8 | var preprocessorOptions = config.enableCoverage ? ['coverage'] : []; 9 | 10 | config.set({ 11 | // Base path that will be used to resolve all patterns (eg. files, exclude). 12 | basePath: '', 13 | frameworks: ['jasmine', 'es6-shim'], 14 | // List of files / patterns to load in the browser. 15 | files: [ 16 | 'third_party/angular-1.6.1/angular.min.js', 17 | 'third_party/angular-1.6.1/angular-aria.min.js', 18 | 'third_party/angular-1.6.1/angular-sanitize.min.js', 19 | 'third_party/angular-1.6.1/angular-mocks.js', 20 | 'third_party/angular-cookies-1.6.1/angular-cookies.min.js', 21 | 'third_party/codemirror-5.19.0/lib/codemirror.js', 22 | 'third_party/codemirror-5.19.0/mode/python/python.js', 23 | 'third_party/skulpt-12272b/skulpt.min.js', 24 | 'third_party/skulpt-12272b/skulpt-stdlib.js', 25 | 'third_party/ui-codemirror-0.3.0/ui-codemirror.min.js', 26 | // Need to explicitly specify question.js first due to the files being 27 | // loaded in descending order. 28 | 'client/question/question.js', 29 | 'client/data/data.js', 30 | 'client/menu/menu.js', 31 | 'client/**/*.js', 32 | 'assets/**/*.js' 33 | ], 34 | '6to5Preprocessor': { 35 | options: { 36 | sourceMap: 'inline' 37 | } 38 | }, 39 | // List of files to exclude. 40 | exclude: [], 41 | // Pre-process matching files before serving them to the browser. 42 | preprocessors: { 43 | 'client/**/*.js': preprocessorOptions, 44 | 'assets/**/*.js': preprocessorOptions 45 | }, 46 | // Test results reporter to use. Possible values: 'dots', 'progress'. 47 | // Available reporters: https://npmjs.org/browse/keyword/karma-reporter. 48 | reporters: ['progress', 'coverage'], 49 | coverageReporter: { 50 | reporters: [{ 51 | type: 'html' 52 | }, { 53 | type: 'json' 54 | }], 55 | subdir: '.', 56 | dir: './karma_coverage_reports' 57 | }, 58 | // Custom launcher (to test without browser access) 59 | customLaunchers: { 60 | 'phantomjs': { 61 | base: 'PhantomJS', 62 | options: { 63 | windowName: 'my-window', 64 | settings: { 65 | webSecurityEnabled: false 66 | }, 67 | }, 68 | flags: ['--load-images=true'], 69 | debug: true 70 | } 71 | }, 72 | // Phantomjs properties 73 | phantomjsLauncher: { 74 | exitOnResourceError: true 75 | }, 76 | // Web server port. 77 | port: 9876, 78 | // Enable / disable colors in the output (reporters and logs). 79 | colors: true, 80 | // Level of logging. 81 | logLevel: config.LOG_INFO, 82 | // Enable / disable watching file and executing tests whenever any file changes. 83 | autoWatch: true, 84 | // Start these browsers. 85 | // Available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 86 | browsers: ['Chrome'], 87 | // Continuous Integration mode. 88 | // If true, Karma captures browsers, runs the tests and exits. 89 | singleRun: true, 90 | // Concurrency level (how many browsers should be started simultaneously). 91 | concurrency: Infinity 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /scripts/run_e2e_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The TIE Authors. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS-IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # TODO(feiluo): Need a way to control the log level to avoid thounsands of lines of log. 18 | 19 | function cleanup { 20 | # Send a kill signal to all protractor processes. The [Pp] is to avoid the 21 | # grep finding the 'grep protractor' process as well. 22 | kill `ps aux | grep [Pp]rotractor | awk '{print $2}'` 23 | 24 | echo Done! 25 | } 26 | 27 | source $(dirname $0)/setup.sh 28 | 29 | # Forces the cleanup function to run on exit. 30 | # Developers: note that at the end of this script, the cleanup() function at 31 | # the top of the file is run. 32 | trap cleanup EXIT 33 | 34 | # Install the following node modules if they aren't already installed. 35 | install_node_module protractor 5.4.0 36 | 37 | # Start up a Selenium Server. 38 | # Note: We use --gecko=false to avoid rate limit reached error. 39 | # See https://github.com/angular/webdriver-manager/issues/307. 40 | ./node_modules/protractor/bin/webdriver-manager update --gecko=false 41 | ./node_modules/protractor/bin/webdriver-manager start & 42 | 43 | # Run the test 44 | ./node_modules/protractor/bin/protractor ./tests/e2e/protractor.conf.js 45 | -------------------------------------------------------------------------------- /scripts/run_karma_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The TIE Authors. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS-IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # NOTE TO DEVELOPERS: Arguments passed into this script will also be passed to 18 | # `karma start`. See CLI options here: 19 | # 20 | # http://karma-runner.github.io/1.0/config/configuration-file.html 21 | 22 | 23 | source $(dirname $0)/setup.sh 24 | 25 | # Install the following node modules if they aren't already installed. 26 | install_node_module jasmine-core 2.5.2 27 | install_node_module karma 1.4.1 28 | install_node_module karma-jasmine 1.1.0 29 | install_node_module karma-chrome-launcher 2.0.0 30 | install_node_module karma-coverage 1.1.1 31 | install_node_module karma-phantomjs-launcher 32 | install_node_module karma-6to5-preprocessor 33 | install_node_module karma-es6-shim 34 | 35 | # Run Karma, passing in any arguments passed to this script. 36 | ./node_modules/karma/bin/karma start "$@" 37 | -------------------------------------------------------------------------------- /scripts/run_linter.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The TIE Authors. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS-IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Usage: 18 | # 19 | # bash scripts/run_linter.sh 20 | 21 | source $(dirname $0)/setup.sh 22 | 23 | # Install the following node modules if they aren't already installed. 24 | install_node_module eslint 3.18.0 25 | 26 | # Run eslint. 27 | ./node_modules/eslint/bin/eslint.js -c ./.eslintrc.json assets/*/*.js client/*.js client/*/*.js client/*/*/*.js client/*/*/*/*.js tests/*.js 28 | -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The TIE Authors. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS-IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # Flags: 18 | # --disable-presubmit-checks: set to disable hooks that run presubmit checks. 19 | 20 | export OS=`uname` 21 | export MACHINE_TYPE=`uname -m` 22 | export TOOLS_DIR=./tools 23 | export NODE_DIR=$TOOLS_DIR/node-8.11.3 24 | if [ ${OS} == "MINGW_NT-10.0" ]; then 25 | export NPM_CMD=$NODE_DIR/npm 26 | else 27 | export NPM_CMD=$NODE_DIR/bin/npm 28 | fi 29 | export NODE_MODULES_DIR=./node_modules 30 | # Adjust PATH to include a reference to node. 31 | export PATH=$NODE_DIR/bin:$PATH 32 | 33 | ############################################################# 34 | # Installs a node module at a particular version. 35 | # Usage: install_node_module [module_name] [module_version] 36 | # Globals: 37 | # NPM_INSTALLED_MODULES 38 | # Arguments: 39 | # module_name: the name of the node module 40 | # module_version: the expected version of the module 41 | # Returns: 42 | # None 43 | ############################################################# 44 | install_node_module() { 45 | if [[ $NPM_INSTALLED_MODULES != *"$1@$2"* ]]; then 46 | echo Installing $1@$2 47 | $NPM_CMD install $1@$2 48 | NPM_INSTALLED_MODULES="$(npm list --silent)" 49 | fi 50 | } 51 | 52 | # Set up hooks if not disabled. 53 | if ! [[ $* == *--disable-presubmit-checks* ]]; then 54 | git config core.hooksPath hooks 55 | chmod u+x hooks/pre-push 56 | fi 57 | 58 | export -f install_node_module 59 | 60 | # Ensure that there is a node_modules folder in the root, otherwise node may 61 | # put libraries in the wrong place. See 62 | # 63 | # https://docs.npmjs.com/files/folders#more-information 64 | if [ ! -d "$NODE_MODULES_DIR" ]; then 65 | mkdir $NODE_MODULES_DIR 66 | fi 67 | 68 | # Download and install node.js, if necessary. 69 | echo Checking if node.js is installed in $NODE_DIR 70 | if [ ! -d "$NODE_DIR" ]; then 71 | echo Installing Node.js for ${OS} 72 | ON_WIN=false 73 | if [ ${OS} == "Darwin" ]; then 74 | if [ ${MACHINE_TYPE} == 'x86_64' ]; then 75 | NODE_FILE_NAME=node-v8.11.3-darwin-x64 76 | else 77 | NODE_FILE_NAME=node-v8.11.3-darwin-x86 78 | fi 79 | elif [ ${OS} == "Linux" ]; then 80 | if [ ${MACHINE_TYPE} == 'x86_64' ]; then 81 | NODE_FILE_NAME=node-v8.11.3-linux-x64 82 | else 83 | NODE_FILE_NAME=node-v8.11.3-linux-x86 84 | fi 85 | elif [ ${OS} == MINGW64_NT-10.0 ]; then 86 | ON_WIN=true 87 | NODE_FILE_NAME=node-v8.11.3-win-x64 88 | fi 89 | 90 | if [ ! -d "$TOOLS_DIR" ]; then 91 | mkdir tools 92 | fi 93 | 94 | 95 | if $ON_WIN; then 96 | curl -o node-download.zip https://nodejs.org/dist/v8.11.3/$NODE_FILE_NAME.zip 97 | unzip node-download.zip -d $TOOLS_DIR 98 | mv $TOOLS_DIR/$NODE_FILE_NAME $NODE_DIR 99 | rm node-download.zip 100 | else 101 | curl -o node-download.tgz https://nodejs.org/dist/v8.11.3/$NODE_FILE_NAME.tar.gz 102 | tar xzf node-download.tgz --directory $TOOLS_DIR 103 | mv $TOOLS_DIR/$NODE_FILE_NAME $NODE_DIR 104 | rm node-download.tgz 105 | fi 106 | fi 107 | 108 | # Generate a list of already-installed modules. 109 | NPM_INSTALLED_MODULES="$(npm list --silent)" 110 | -------------------------------------------------------------------------------- /tests/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The TIE Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Configuration values for TIE Protractor E2E tests. 17 | */ 18 | 19 | exports.config = { 20 | framework: 'jasmine', 21 | capabilities: { 22 | browserName: 'chrome', 23 | loggingPrefs: { 24 | browser: 'ALL', 25 | }, 26 | }, 27 | specs: ['./*.spec.js'], 28 | onPrepare: async function() { 29 | // By default, Protractor use data:text/html, as resetUrl, but 30 | // location.replace from the data: to the file: protocol is not allowed 31 | // (we'll get ‘not allowed local resource’ error), so we replace resetUrl 32 | // with the file: protocol (this will display system's root folder). 33 | browser.resetUrl = 'file://' + __dirname.replace('/tests/e2e', ''); 34 | browser.baseUrl = 'file://' + __dirname.replace('/tests/e2e', ''); 35 | }, 36 | jasmineNodeOpts: { 37 | // Maxmium time to run an "it" block 38 | // https://www.protractortest.org/#/timeouts#timeouts-from-jasmine 39 | defaultTimeoutInterval: 60 * 1000, 40 | }, 41 | allScriptsTimeout: 60 * 1000, 42 | SELENIUM_PROMISE_MANAGER: false, 43 | params: { 44 | defaultQuestionId: 'reverseWords', 45 | questionPage: require('./question.pageObject.js'), 46 | utils: require('./utils.js') 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /third_party/angular-1.6.1/angular-aria.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.6.1 3 | (c) 2010-2016 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(s,p){'use strict';var c="BUTTON A INPUT TEXTAREA SELECT DETAILS SUMMARY".split(" "),h=function(a,b){if(-1!==b.indexOf(a[0].nodeName))return!0};p.module("ngAria",["ng"]).provider("$aria",function(){function a(a,c,n,k){return function(d,f,e){var g=e.$normalize(c);!b[g]||h(f,n)||e[g]||d.$watch(e[a],function(a){a=k?!a:!!a;f.attr(c,a)})}}var b={ariaHidden:!0,ariaChecked:!0,ariaReadonly:!0,ariaDisabled:!0,ariaRequired:!0,ariaInvalid:!0,ariaValue:!0,tabindex:!0,bindKeydown:!0,bindRoleForClick:!0}; 7 | this.config=function(a){b=p.extend(b,a)};this.$get=function(){return{config:function(a){return b[a]},$$watchExpr:a}}}).directive("ngShow",["$aria",function(a){return a.$$watchExpr("ngShow","aria-hidden",[],!0)}]).directive("ngHide",["$aria",function(a){return a.$$watchExpr("ngHide","aria-hidden",[],!1)}]).directive("ngValue",["$aria",function(a){return a.$$watchExpr("ngValue","aria-checked",c,!1)}]).directive("ngChecked",["$aria",function(a){return a.$$watchExpr("ngChecked","aria-checked",c,!1)}]).directive("ngReadonly", 8 | ["$aria",function(a){return a.$$watchExpr("ngReadonly","aria-readonly",c,!1)}]).directive("ngRequired",["$aria",function(a){return a.$$watchExpr("ngRequired","aria-required",c,!1)}]).directive("ngModel",["$aria",function(a){function b(b,k,d,f){return a.config(k)&&!d.attr(b)&&(f||!h(d,c))}function l(a,b){return!b.attr("role")&&b.attr("type")===a&&!h(b,c)}function m(a,b){var d=a.type,f=a.role;return"checkbox"===(d||f)||"menuitemcheckbox"===f?"checkbox":"radio"===(d||f)||"menuitemradio"===f?"radio": 9 | "range"===d||"progressbar"===f||"slider"===f?"range":""}return{restrict:"A",require:"ngModel",priority:200,compile:function(c,k){var d=m(k,c);return{post:function(f,e,g,c){function k(){return c.$modelValue}function h(a){e.attr("aria-checked",g.value==c.$viewValue)}function m(){e.attr("aria-checked",!c.$isEmpty(c.$viewValue))}var n=b("tabindex","tabindex",e,!1);switch(d){case "radio":case "checkbox":l(d,e)&&e.attr("role",d);b("aria-checked","ariaChecked",e,!1)&&f.$watch(k,"radio"===d?h:m);n&&e.attr("tabindex", 10 | 0);break;case "range":l(d,e)&&e.attr("role","slider");if(a.config("ariaValue")){var p=!e.attr("aria-valuemin")&&(g.hasOwnProperty("min")||g.hasOwnProperty("ngMin")),q=!e.attr("aria-valuemax")&&(g.hasOwnProperty("max")||g.hasOwnProperty("ngMax")),r=!e.attr("aria-valuenow");p&&g.$observe("min",function(a){e.attr("aria-valuemin",a)});q&&g.$observe("max",function(a){e.attr("aria-valuemax",a)});r&&f.$watch(k,function(a){e.attr("aria-valuenow",a)})}n&&e.attr("tabindex",0)}!g.hasOwnProperty("ngRequired")&& 11 | c.$validators.required&&b("aria-required","ariaRequired",e,!1)&&g.$observe("required",function(){e.attr("aria-required",!!g.required)});b("aria-invalid","ariaInvalid",e,!0)&&f.$watch(function(){return c.$invalid},function(a){e.attr("aria-invalid",!!a)})}}}}}]).directive("ngDisabled",["$aria",function(a){return a.$$watchExpr("ngDisabled","aria-disabled",c,!1)}]).directive("ngMessages",function(){return{restrict:"A",require:"?ngMessages",link:function(a,b,c,h){b.attr("aria-live")||b.attr("aria-live", 12 | "assertive")}}}).directive("ngClick",["$aria","$parse",function(a,b){return{restrict:"A",compile:function(l,m){var n=b(m.ngClick,null,!0);return function(b,d,f){if(!h(d,c)&&(a.config("bindRoleForClick")&&!d.attr("role")&&d.attr("role","button"),a.config("tabindex")&&!d.attr("tabindex")&&d.attr("tabindex",0),a.config("bindKeydown")&&!f.ngKeydown&&!f.ngKeypress&&!f.ngKeyup))d.on("keydown",function(a){function c(){n(b,{$event:a})}var d=a.which||a.keyCode;32!==d&&13!==d||b.$apply(c)})}}}}]).directive("ngDblclick", 13 | ["$aria",function(a){return function(b,l,m){!a.config("tabindex")||l.attr("tabindex")||h(l,c)||l.attr("tabindex",0)}}])})(window,window.angular); 14 | //# sourceMappingURL=angular-aria.min.js.map 15 | -------------------------------------------------------------------------------- /third_party/angular-cookies-1.6.1/angular-cookies.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.6.1 3 | (c) 2010-2016 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(n,c){'use strict';function l(b,a,g){var d=g.baseHref(),k=b[0];return function(b,e,f){var g,h;f=f||{};h=f.expires;g=c.isDefined(f.path)?f.path:d;c.isUndefined(e)&&(h="Thu, 01 Jan 1970 00:00:00 GMT",e="");c.isString(h)&&(h=new Date(h));e=encodeURIComponent(b)+"="+encodeURIComponent(e);e=e+(g?";path="+g:"")+(f.domain?";domain="+f.domain:"");e+=h?";expires="+h.toUTCString():"";e+=f.secure?";secure":"";f=e.length+1;4096 4096 bytes)!");k.cookie=e}}c.module("ngCookies",["ng"]).provider("$cookies",[function(){var b=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(a,g){return{get:function(d){return a()[d]},getObject:function(d){return(d=this.get(d))?c.fromJson(d):d},getAll:function(){return a()},put:function(d,a,m){g(d,a,m?c.extend({},b,m):b)},putObject:function(d,b,a){this.put(d,c.toJson(b),a)},remove:function(a,k){g(a,void 0,k?c.extend({},b,k):b)}}}]}]);c.module("ngCookies").factory("$cookieStore", 8 | ["$cookies",function(b){return{get:function(a){return b.getObject(a)},put:function(a,c){b.putObject(a,c)},remove:function(a){b.remove(a)}}}]);l.$inject=["$document","$log","$browser"];c.module("ngCookies").provider("$$cookieWriter",function(){this.$get=l})})(window,window.angular); 9 | //# sourceMappingURL=angular-cookies.min.js.map 10 | -------------------------------------------------------------------------------- /third_party/angular-cookies-1.6.1/angular-cookies.min.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version":3, 3 | "file":"angular-cookies.min.js", 4 | "lineCount":8, 5 | "mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkB,CAmR3BC,QAASA,EAAc,CAACC,CAAD,CAAYC,CAAZ,CAAkBC,CAAlB,CAA4B,CACjD,IAAIC,EAAaD,CAAAE,SAAA,EAAjB,CACIC,EAAcL,CAAA,CAAU,CAAV,CAmClB,OAAO,SAAQ,CAACM,CAAD,CAAOC,CAAP,CAAcC,CAAd,CAAuB,CAjCW,IAC3CC,CAD2C,CACrCC,CACVF,EAAA,CAgCoDA,CAhCpD,EAAqB,EACrBE,EAAA,CAAUF,CAAAE,QACVD,EAAA,CAAOX,CAAAa,UAAA,CAAkBH,CAAAC,KAAlB,CAAA,CAAkCD,CAAAC,KAAlC,CAAiDN,CACpDL,EAAAc,YAAA,CAAoBL,CAApB,CAAJ,GACEG,CACA,CADU,+BACV,CAAAH,CAAA,CAAQ,EAFV,CAIIT,EAAAe,SAAA,CAAiBH,CAAjB,CAAJ,GACEA,CADF,CACY,IAAII,IAAJ,CAASJ,CAAT,CADZ,CAIIK,EAAAA,CAAMC,kBAAA,CAqB6BV,CArB7B,CAANS,CAAiC,GAAjCA,CAAuCC,kBAAA,CAAmBT,CAAnB,CAE3CQ,EAAA,CADAA,CACA,EADON,CAAA,CAAO,QAAP,CAAkBA,CAAlB,CAAyB,EAChC,GAAOD,CAAAS,OAAA,CAAiB,UAAjB,CAA8BT,CAAAS,OAA9B,CAA+C,EAAtD,CACAF,EAAA,EAAOL,CAAA,CAAU,WAAV,CAAwBA,CAAAQ,YAAA,EAAxB,CAAgD,EACvDH,EAAA,EAAOP,CAAAW,OAAA,CAAiB,SAAjB,CAA6B,EAMhCC,EAAAA,CAAeL,CAAAM,OAAfD,CAA4B,CACb,KAAnB,CAAIA,CAAJ,EACEnB,CAAAqB,KAAA,CAAU,UAAV,CASqChB,CATrC,CACE,6DADF;AAEEc,CAFF,CAEiB,iBAFjB,CASFf,EAAAkB,OAAA,CAJOR,CAG6B,CArCW,CAjQnDjB,CAAA0B,OAAA,CAAe,WAAf,CAA4B,CAAC,IAAD,CAA5B,CAAAC,SAAA,CAOY,UAPZ,CAOwB,CAAaC,QAAyB,EAAG,CAkC7D,IAAIC,EAAW,IAAAA,SAAXA,CAA2B,EAiC/B,KAAAC,KAAA,CAAY,CAAC,gBAAD,CAAmB,gBAAnB,CAAqC,QAAQ,CAACC,CAAD,CAAiBC,CAAjB,CAAiC,CACxF,MAAO,CAWLC,IAAKA,QAAQ,CAACC,CAAD,CAAM,CACjB,MAAOH,EAAA,EAAA,CAAiBG,CAAjB,CADU,CAXd,CAyBLC,UAAWA,QAAQ,CAACD,CAAD,CAAM,CAEvB,MAAO,CADHzB,CACG,CADK,IAAAwB,IAAA,CAASC,CAAT,CACL,EAAQlC,CAAAoC,SAAA,CAAiB3B,CAAjB,CAAR,CAAkCA,CAFlB,CAzBpB,CAuCL4B,OAAQA,QAAQ,EAAG,CACjB,MAAON,EAAA,EADU,CAvCd,CAuDLO,IAAKA,QAAQ,CAACJ,CAAD,CAAMzB,CAAN,CAAaC,CAAb,CAAsB,CACjCsB,CAAA,CAAeE,CAAf,CAAoBzB,CAApB,CAAuCC,CAvFpC,CAAUV,CAAAuC,OAAA,CAAe,EAAf,CAAmBV,CAAnB,CAuF0BnB,CAvF1B,CAAV,CAAkDmB,CAuFrD,CADiC,CAvD9B,CAuELW,UAAWA,QAAQ,CAACN,CAAD,CAAMzB,CAAN,CAAaC,CAAb,CAAsB,CACvC,IAAA4B,IAAA,CAASJ,CAAT,CAAclC,CAAAyC,OAAA,CAAehC,CAAf,CAAd,CAAqCC,CAArC,CADuC,CAvEpC,CAsFLgC,OAAQA,QAAQ,CAACR,CAAD,CAAMxB,CAAN,CAAe,CAC7BsB,CAAA,CAAeE,CAAf,CAAoBS,IAAAA,EAApB,CAA2CjC,CAtHxC,CAAUV,CAAAuC,OAAA,CAAe,EAAf,CAAmBV,CAAnB,CAsH8BnB,CAtH9B,CAAV,CAAkDmB,CAsHrD,CAD6B,CAtF1B,CADiF,CAA9E,CAnEiD,CAAzC,CAPxB,CAwKA7B,EAAA0B,OAAA,CAAe,WAAf,CAAAkB,QAAA,CA+BS,cA/BT;AA+ByB,CAAC,UAAD,CAAa,QAAQ,CAACC,CAAD,CAAW,CAErD,MAAO,CAWLZ,IAAKA,QAAQ,CAACC,CAAD,CAAM,CACjB,MAAOW,EAAAV,UAAA,CAAmBD,CAAnB,CADU,CAXd,CAyBLI,IAAKA,QAAQ,CAACJ,CAAD,CAAMzB,CAAN,CAAa,CACxBoC,CAAAL,UAAA,CAAmBN,CAAnB,CAAwBzB,CAAxB,CADwB,CAzBrB,CAsCLiC,OAAQA,QAAQ,CAACR,CAAD,CAAM,CACpBW,CAAAH,OAAA,CAAgBR,CAAhB,CADoB,CAtCjB,CAF8C,CAAhC,CA/BzB,CAmIAjC,EAAA6C,QAAA,CAAyB,CAAC,WAAD,CAAc,MAAd,CAAsB,UAAtB,CAEzB9C,EAAA0B,OAAA,CAAe,WAAf,CAAAC,SAAA,CAAqC,gBAArC,CAAoEoB,QAA+B,EAAG,CACpG,IAAAjB,KAAA,CAAY7B,CADwF,CAAtG,CA/T2B,CAA1B,CAAD,CAoUGF,MApUH,CAoUWA,MAAAC,QApUX;", 6 | "sources":["angular-cookies.js"], 7 | "names":["window","angular","$$CookieWriter","$document","$log","$browser","cookiePath","baseHref","rawDocument","name","value","options","path","expires","isDefined","isUndefined","isString","Date","str","encodeURIComponent","domain","toUTCString","secure","cookieLength","length","warn","cookie","module","provider","$CookiesProvider","defaults","$get","$$cookieReader","$$cookieWriter","get","key","getObject","fromJson","getAll","put","extend","putObject","toJson","remove","undefined","factory","$cookies","$inject","$$CookieWriterProvider"] 8 | } 9 | -------------------------------------------------------------------------------- /third_party/codemirror-5.19.0/lib/mbo.css: -------------------------------------------------------------------------------- 1 | /****************************************************************/ 2 | /* Based on mbonaci's Brackets mbo theme */ 3 | /* https://github.com/mbonaci/global/blob/master/Mbo.tmTheme */ 4 | /* Create your own: http://tmtheme-editor.herokuapp.com */ 5 | /****************************************************************/ 6 | 7 | .cm-s-mbo.CodeMirror { background: #2c2c2c; color: #ffffec; } 8 | .cm-s-mbo div.CodeMirror-selected { background: #716C62; } 9 | .cm-s-mbo .CodeMirror-line::selection, .cm-s-mbo .CodeMirror-line > span::selection, .cm-s-mbo .CodeMirror-line > span > span::selection { background: rgba(113, 108, 98, .99); } 10 | .cm-s-mbo .CodeMirror-line::-moz-selection, .cm-s-mbo .CodeMirror-line > span::-moz-selection, .cm-s-mbo .CodeMirror-line > span > span::-moz-selection { background: rgba(113, 108, 98, .99); } 11 | .cm-s-mbo .CodeMirror-gutters { background: #4e4e4e; border-right: 0px; } 12 | .cm-s-mbo .CodeMirror-guttermarker { color: white; } 13 | .cm-s-mbo .CodeMirror-guttermarker-subtle { color: grey; } 14 | .cm-s-mbo .CodeMirror-linenumber { color: #dadada; } 15 | .cm-s-mbo .CodeMirror-cursor { border-left: 1px solid #ffffec; } 16 | 17 | .cm-s-mbo span.cm-comment { color: #95958a; } 18 | .cm-s-mbo span.cm-atom { color: #00a8c6; } 19 | .cm-s-mbo span.cm-number { color: #00a8c6; } 20 | 21 | .cm-s-mbo span.cm-property, .cm-s-mbo span.cm-attribute { color: #9ddfe9; } 22 | .cm-s-mbo span.cm-keyword { color: #ffb928; } 23 | .cm-s-mbo span.cm-string { color: #ffcf6c; } 24 | .cm-s-mbo span.cm-string.cm-property { color: #ffffec; } 25 | 26 | .cm-s-mbo span.cm-variable { color: #ffffec; } 27 | .cm-s-mbo span.cm-variable-2 { color: #00a8c6; } 28 | .cm-s-mbo span.cm-def { color: #ffffec; } 29 | .cm-s-mbo span.cm-bracket { color: #fffffc; font-weight: bold; } 30 | .cm-s-mbo span.cm-tag { color: #9ddfe9; } 31 | .cm-s-mbo span.cm-link { color: #f54b07; } 32 | .cm-s-mbo span.cm-error { border-bottom: #636363; color: #ffffec; } 33 | .cm-s-mbo span.cm-qualifier { color: #ffffec; } 34 | 35 | .cm-s-mbo .CodeMirror-activeline-background { background: #494b41; } 36 | .cm-s-mbo .CodeMirror-matchingbracket { color: #ffb928 !important; } 37 | .cm-s-mbo .CodeMirror-matchingtag { background: rgba(255, 255, 255, .37); } -------------------------------------------------------------------------------- /third_party/ui-codemirror-0.3.0/ui-codemirror.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-ui-codemirror - This directive allows you to add CodeMirror to your textarea elements. 3 | * @version v0.3.0 - 2015-05-03 4 | * @link http://angular-ui.github.com 5 | * @license MIT 6 | */ 7 | "use strict";function uiCodemirrorDirective(a,b){function c(a,c,h,i){var j=angular.extend({value:c.text()},b.codemirror||{},a.$eval(h.uiCodemirror),a.$eval(h.uiCodemirrorOpts)),k=d(c,j);e(k,h.uiCodemirror||h.uiCodemirrorOpts,a),f(k,i,a),g(k,h.uiRefresh,a),a.$on("CodeMirror",function(a,b){if(!angular.isFunction(b))throw new Error("the CodeMirror event requires a callback function");b(k)}),angular.isFunction(j.onLoad)&&j.onLoad(k)}function d(a,b){var c;return"TEXTAREA"===a[0].tagName?c=window.CodeMirror.fromTextArea(a[0],b):(a.html(""),c=new window.CodeMirror(function(b){a.append(b)},b)),c}function e(a,b,c){function d(b,c){angular.isObject(b)&&e.forEach(function(d){if(b.hasOwnProperty(d)){if(c&&b[d]===c[d])return;a.setOption(d,b[d])}})}if(b){var e=Object.keys(window.CodeMirror.defaults);c.$watch(b,d,!0)}}function f(a,b,c){b&&(b.$formatters.push(function(a){if(angular.isUndefined(a)||null===a)return"";if(angular.isObject(a)||angular.isArray(a))throw new Error("ui-codemirror cannot use an object or an array as a model");return a}),b.$render=function(){var c=b.$viewValue||"";a.setValue(c)},a.on("change",function(a){var d=a.getValue();d!==b.$viewValue&&c.$evalAsync(function(){b.$setViewValue(d)})}))}function g(b,c,d){c&&d.$watch(c,function(c,d){c!==d&&a(function(){b.refresh()})})}return{restrict:"EA",require:"?ngModel",compile:function(){if(angular.isUndefined(window.CodeMirror))throw new Error("ui-codemirror needs CodeMirror to work... (o rly?)");return c}}}angular.module("ui.codemirror",[]).constant("uiCodemirrorConfig",{}).directive("uiCodemirror",uiCodemirrorDirective),uiCodemirrorDirective.$inject=["$timeout","uiCodemirrorConfig"]; 8 | --------------------------------------------------------------------------------