├── .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 [](https://travis-ci.org/google/tie) [](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 |