diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c0bd32f..c30a07b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,23 +1,27 @@ -name: Python application +name: feedbackxblock on: push: - branches: [ master, main ] + branches: [master, main] pull_request: - branches: [ master, main ] jobs: - build: - - runs-on: ubuntu-latest - + test: + runs-on: ubuntu-18.04 + strategy: + matrix: + python-version: [2.7, 3.5, 3.8] + tox-env: + - quality + - py steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.5 - uses: actions/setup-python@v2 - with: - python-version: 3.5 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install tox + - name: Test with tox + run: tox -e ${{ matrix.tox-env }} diff --git a/.gitignore b/.gitignore index 915fc00..e3b41d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ *pyc rate_xblock.egg-info -*~ \ No newline at end of file +*~ +.coverage +.tox +*.egg-info +__pycache__ \ No newline at end of file diff --git a/feedback/__init__.py b/feedback/__init__.py index 90a76c1..a7e02e6 100644 --- a/feedback/__init__.py +++ b/feedback/__init__.py @@ -3,5 +3,3 @@ course resources, and to think and synthesize about their experience in the course. """ - -from .feedback import FeedbackXBlock diff --git a/feedback/feedback.py b/feedback/feedback.py index f0c2892..7e684d7 100644 --- a/feedback/feedback.py +++ b/feedback/feedback.py @@ -7,12 +7,13 @@ import cgi import random - import pkg_resources +import six from xblock.core import XBlock from xblock.fields import Scope, Integer, String, List, Float -from xblock.fragment import Fragment +from web_fragments.fragment import Fragment + # We provide default text which is designed to elicit student thought. We'd # like instructors to customize this to something highly structured (not @@ -31,7 +32,7 @@ # Unicode alt faces are cute, but we do nulls instead for a11y. ICON_SETS = {'face': [""]*5, # u"😁😊😐😞😭", 'num': u"12345", - 'midface': [""]*5} #u"😞😐😊😐😞"} + 'midface': [""]*5} # u"😞😐😊😐😞"} @XBlock.needs('i18n') @@ -264,9 +265,9 @@ def studio_view(self, context): prompt = self.get_prompt(0) for idx in range(len(prompt['scale_text'])): prompt['likert{i}'.format(i=idx)] = prompt['scale_text'][idx] - frag = Fragment(unicode(html_str).format(**prompt)) + frag = Fragment(six.text_type(html_str).format(**prompt)) js_str = self.resource_string("static/js/src/studio.js") - frag.add_javascript(unicode(js_str)) + frag.add_javascript(six.text_type(js_str)) frag.initialize_js('FeedbackBlock', {'icon_set': prompt['icon_set']}) return frag @@ -304,7 +305,7 @@ def vote(self, data): """ # prompt_choice is initialized by student view. # Ideally, we'd break this out into a function. - prompt = self.get_prompt(self.prompt_choice) + prompt = self.get_prompt(self.prompt_choice) # noqa # Make sure we're initialized self.init_vote_aggregate() diff --git a/feedbacktests/__init__.py b/feedbacktests/__init__.py index 823129e..e69de29 100644 --- a/feedbacktests/__init__.py +++ b/feedbacktests/__init__.py @@ -1 +0,0 @@ -from .test_feedback import TestFeedback diff --git a/feedbacktests/conftest.py b/feedbacktests/conftest.py new file mode 100644 index 0000000..1328b5f --- /dev/null +++ b/feedbacktests/conftest.py @@ -0,0 +1,27 @@ +import pytest +from mock import Mock + +from workbench.runtime import WorkbenchRuntime +from xblock.fields import ScopeIds +from xblock.runtime import DictKeyValueStore, KvsFieldData + +from feedback.feedback import FeedbackXBlock + + +def generate_scope_ids(runtime, block_type): + """ helper to generate scope IDs for an XBlock """ + def_id = runtime.id_generator.create_definition(block_type) + usage_id = runtime.id_generator.create_usage(def_id) + return ScopeIds('user', block_type, def_id, usage_id) + + +@pytest.fixture +def feedback_xblock(): + """Feedback XBlock pytest fixture.""" + runtime = WorkbenchRuntime() + key_store = DictKeyValueStore() + db_model = KvsFieldData(key_store) + ids = generate_scope_ids(runtime, 'feedback') + feedback_xblock = FeedbackXBlock(runtime, db_model, scope_ids=ids) + feedback_xblock.usage_id = Mock() + return feedback_xblock diff --git a/feedbacktests/test_feedback.py b/feedbacktests/test_feedback.py index cd467ad..96b44e4 100644 --- a/feedbacktests/test_feedback.py +++ b/feedbacktests/test_feedback.py @@ -1,9 +1,7 @@ ''' -Tests for the FeedbackXBlock. +Tests for the FeedbackXBlock that needs to run in Open edX. ''' -import json -import sys from openedx.tests.xblock_integration.xblock_testcase import XBlockTestCase import mock @@ -31,7 +29,7 @@ def set_random(self, random_patch_value): # pylint: disable=abstract-method -class TestFeedback(PatchRandomMixin, XBlockTestCase): +class FeedbackTestCase(PatchRandomMixin, XBlockTestCase): """ Basic tests for the FeedbackXBlock. We set up a page with two of the block, make sure the page renders, toggle a few ratings, diff --git a/feedbacktests/test_feedback_unit.py b/feedbacktests/test_feedback_unit.py new file mode 100644 index 0000000..2de642c --- /dev/null +++ b/feedbacktests/test_feedback_unit.py @@ -0,0 +1,52 @@ +""" +Tests for the Feedback XBlock with heavy mocking. +""" + +from mock import Mock + + +def test_template_content(feedback_xblock): + """ Test content of FeedbackXBlock's student view """ + student_fragment = feedback_xblock.render('student_view', Mock()) + assert 'feedback' in student_fragment.content + + +def test_studio_view(feedback_xblock): + """ Test content of FeedbackXBlock's author view """ + student_fragment = feedback_xblock.render('studio_view', Mock()) + assert 'feedback' in student_fragment.content + + +def test_studio_submit(feedback_xblock): + """ Test the FeedbackXBlock's save action """ + request_body = b"""{ + "display_name": "foo" + }""" + request = Mock(method='POST', body=request_body) + response = feedback_xblock.studio_submit(request) + assert response.status_code == 200 and {'result': 'success'} == response.json, response.json + + +def test_vote(feedback_xblock): + """ Test content of FeedbackXBlock's vote() method """ + feedback_xblock.vote({'vote': 1}) + + +def test_feedback_method(feedback_xblock): + """ Test content of FeedbackXBlock's feedback() method """ + request_body = b"""{ + "freeform": "yes", + "vote": 1 + }""" + request = Mock(method='POST', body=request_body) + response = feedback_xblock.feedback(request) + + expected_response_json = { + "aggregate": [0, 1, 0, 0, 0], + "freeform": "yes", + "response": "Thank you for your feedback!", + "success": True, + "vote": 1, + } + + assert response.status_code == 200 and response.json == expected_response_json, response.json diff --git a/geckodriver.log b/geckodriver.log new file mode 100644 index 0000000..e69de29 diff --git a/makeicons/test_icons.py b/makeicons/test_icons.py index b59d1a5..a07fc03 100644 --- a/makeicons/test_icons.py +++ b/makeicons/test_icons.py @@ -9,7 +9,7 @@ import unittest from bok_choy.web_app_test import WebAppTest -from pages import IconsPage +from .pages import IconsPage class TestIcons(WebAppTest): @@ -29,5 +29,6 @@ def test_page_existence(self): self.assertScreenshot("#"+style+icon+str(i+1), style+icon+str(i+1)) + if __name__ == '__main__': unittest.main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8543341 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,25 @@ +bok-choy>=1.1.1 + +Django>=1.11,<2; python_version < '3' +Django>=2.2,<2.3; python_version >= '3' + +django-pyfs>=2.2 +flake8>=3.8.4 +mock>=3.0.5 + +pytest==4.6.11; python_version < '3' +pytest>=6.1.2; python_version >= '3' + +pytest-cov>=2.10.1 + +pytest-django>=3.10.0; python_version < '3' +pytest-django>=4.1.0; python_version >= '3' + +six>=1.15.0 + +web-fragments>=0.3.2 + +XBlock==1.2.1; python_version < '3' +XBlock>=1.4.0; python_version >= '3' + +xblock-sdk>=0.2.2 diff --git a/setup.py b/setup.py index d5bf8a1..a26cc6d 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def package_data(pkg, roots): ], entry_points={ 'xblock.v1': [ - 'feedback = feedback:FeedbackXBlock', + 'feedback = feedback.feedback:FeedbackXBlock', ], 'xblock.test.v0': [ 'feedbacktest = feedbacktests:TestFeedback', diff --git a/test_settings.py b/test_settings.py new file mode 100644 index 0000000..37a7563 --- /dev/null +++ b/test_settings.py @@ -0,0 +1,7 @@ +""" +Test settings for the Feedback XBlock. +""" + +from workbench.settings import * + +from django.conf.global_settings import LOGGING diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..4e19a66 --- /dev/null +++ b/tox.ini @@ -0,0 +1,26 @@ +[tox] +envlist = py{27,35,38},quality +skipsdist = True + +[pytest] +addopts = --cov=feedback --cov-report=term-missing + +[testenv] +usedevelop=True +passenv = + SELENIUM_BROWSER +setenv = + DJANGO_SETTINGS_MODULE = test_settings +deps = + -r{toxinidir}/requirements.txt +commands = + # TODO: Activate the rest of the tests once they're fixed + {posargs:pytest feedbacktests/test_feedback_unit.py} + +[flake8] +max-line-length = 160 + +[testenv:quality] +deps = flake8 +commands = + flake8 feedback feedbacktests makeicons setup.py