Skip to content

Commit

Permalink
feat: Adds xblock-utils repository code into this repository
Browse files Browse the repository at this point in the history
  • Loading branch information
farhan committed Sep 12, 2023
1 parent 8155eae commit 76eaea3
Show file tree
Hide file tree
Showing 32 changed files with 1,941 additions and 3 deletions.
7 changes: 7 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pytest


# https://pytest-django.readthedocs.io/en/latest/faq.html#how-can-i-give-database-access-to-all-my-tests-without-the-django-db-marker
@pytest.fixture(autouse=True)
def enable_db_access_for_all_tests(db):
pass
1 change: 1 addition & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

fs
lxml
mako
markupsafe
python-dateutil
pytz
Expand Down
1 change: 1 addition & 0 deletions requirements/test.in
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ pytest
pytest-cov
pytest-django
tox
xblock-sdk
1 change: 1 addition & 0 deletions xblock/test/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@

# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
'workbench'
)

# A sample logging configuration. The only tangible logging
Expand Down
6 changes: 3 additions & 3 deletions xblock/test/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ def _num_plugins_cached():
return len(plugin.PLUGIN_CACHE)


@XBlock.register_temp_plugin(AmbiguousBlock1, "thumbs")
@XBlock.register_temp_plugin(AmbiguousBlock1, "thumbs_1")
def test_plugin_caching():
plugin.PLUGIN_CACHE = {}
assert _num_plugins_cached() == 0

XBlock.load_class("thumbs")
XBlock.load_class("thumbs_1")
assert _num_plugins_cached() == 1

XBlock.load_class("thumbs")
XBlock.load_class("thumbs_1")
assert _num_plugins_cached() == 1
Empty file added xblock/test/utils/__init__.py
Empty file.
Empty file.
1 change: 1 addition & 0 deletions xblock/test/utils/data/another_template.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<explanation>This is an even simpler xml template.</explanation>
3 changes: 3 additions & 0 deletions xblock/test/utils/data/l10n_django_template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% load l10n %}
{{ 1000|localize }}
{{ 1000|unlocalize }}
14 changes: 14 additions & 0 deletions xblock/test/utils/data/simple_django_template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
This is a simple template example.

This template can make use of the following context variables:
Name: {{name}}
List: {{items|safe}}

It can also do some fancy things with them:
Default value if name is empty: {{name|default:"Default Name"}}
Length of the list: {{items|length}}
Items of the list:{% for item in items %} {{item}}{% endfor %}

Although it is simple, it can also contain non-ASCII characters:

Thé Fütüré øf Ønlïné Édüçätïøn Ⱡσяєм ι# Før änýøné, änýwhéré, änýtïmé Ⱡσяєм #
18 changes: 18 additions & 0 deletions xblock/test/utils/data/simple_mako_template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
This is a simple template example.

This template can make use of the following context variables:
Name: ${name}
List: ${items}

It can also do some fancy things with them:
Default value if name is empty: ${ name or "Default Name"}
Length of the list: ${len(items)}
Items of the list:\
% for item in items:
${item}\
% endfor


Although it is simple, it can also contain non-ASCII characters:

Thé Fütüré øf Ønlïné Édüçätïøn Ⱡσяєм ι# Før änýøné, änýwhéré, änýtïmé Ⱡσяєм #
6 changes: 6 additions & 0 deletions xblock/test/utils/data/simple_template.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<example>
<title>This is a simple xml template.</title>
<arguments>
<url_name>{{url_name}}</url_name>
</arguments>
</example>
8 changes: 8 additions & 0 deletions xblock/test/utils/data/trans_django_template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% load i18n %}
{% trans "Translate 1" %}
{% trans "Translate 2" as var %}
{{ var }}
{% blocktrans %}
Multi-line translation
with variable: {{name}}
{% endblocktrans %}
Binary file not shown.
29 changes: 29 additions & 0 deletions xblock/test/utils/data/translations/eo/LC_MESSAGES/text.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2016-03-30 16:54+0500\n"
"PO-Revision-Date: 2016-03-30 16:54+0500\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: eo\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: data/trans_django_template.txt
msgid "Translate 1"
msgstr "tRaNsLaTe !"

#: data/trans_django_template.txt
msgid "Translate 2"
msgstr ""

#: data/trans_django_template.txt
msgid ""
"\n"
"Multi-line translation"
"\n"
"with variable: %(name)s"
"\n"
msgstr "\nmUlTi_LiNe TrAnSlAtIoN: %(name)s\n"
78 changes: 78 additions & 0 deletions xblock/test/utils/test_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
Tests for helpers.py
"""

import unittest

from workbench.runtime import WorkbenchRuntime

from xblock.core import XBlock
from xblock.utils.helpers import child_isinstance


class DogXBlock(XBlock):
""" Test XBlock representing any dog. Raises error if instantiated. """
pass


class GoldenRetrieverXBlock(DogXBlock):
""" Test XBlock representing a golden retriever """
pass


class CatXBlock(XBlock):
""" Test XBlock representing any cat """
pass


class BasicXBlock(XBlock):
""" Basic XBlock """
has_children = True


class TestChildIsInstance(unittest.TestCase):
"""
Test child_isinstance helper method, in the workbench runtime.
"""

@XBlock.register_temp_plugin(GoldenRetrieverXBlock, "gr")
@XBlock.register_temp_plugin(CatXBlock, "cat")
@XBlock.register_temp_plugin(BasicXBlock, "block")
def test_child_isinstance(self):
"""
Check that child_isinstance() works on direct children
"""
self.runtime = WorkbenchRuntime()
self.root_id = self.runtime.parse_xml_string('<block> <block><cat/><gr/></block> <cat/> <gr/> </block>')
root = self.runtime.get_block(self.root_id)
self.assertFalse(child_isinstance(root, root.children[0], DogXBlock))
self.assertFalse(child_isinstance(root, root.children[0], GoldenRetrieverXBlock))
self.assertTrue(child_isinstance(root, root.children[0], BasicXBlock))

self.assertFalse(child_isinstance(root, root.children[1], DogXBlock))
self.assertFalse(child_isinstance(root, root.children[1], GoldenRetrieverXBlock))
self.assertTrue(child_isinstance(root, root.children[1], CatXBlock))

self.assertFalse(child_isinstance(root, root.children[2], CatXBlock))
self.assertTrue(child_isinstance(root, root.children[2], DogXBlock))
self.assertTrue(child_isinstance(root, root.children[2], GoldenRetrieverXBlock))

@XBlock.register_temp_plugin(GoldenRetrieverXBlock, "gr")
@XBlock.register_temp_plugin(CatXBlock, "cat")
@XBlock.register_temp_plugin(BasicXBlock, "block")
def test_child_isinstance_descendants(self):
"""
Check that child_isinstance() works on deeper descendants
"""
self.runtime = WorkbenchRuntime()
self.root_id = self.runtime.parse_xml_string('<block> <block><cat/><gr/></block> <cat/> <gr/> </block>')
root = self.runtime.get_block(self.root_id)
block = root.runtime.get_block(root.children[0])
self.assertIsInstance(block, BasicXBlock)

self.assertFalse(child_isinstance(root, block.children[0], DogXBlock))
self.assertTrue(child_isinstance(root, block.children[0], CatXBlock))

self.assertTrue(child_isinstance(root, block.children[1], DogXBlock))
self.assertTrue(child_isinstance(root, block.children[1], GoldenRetrieverXBlock))
self.assertFalse(child_isinstance(root, block.children[1], CatXBlock))
112 changes: 112 additions & 0 deletions xblock/test/utils/test_publish_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#
# Copyright (C) 2014-2015 edX
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#


import unittest

import simplejson as json

from xblock.utils.publish_event import PublishEventMixin


class EmptyMock():
pass


class RequestMock:
method = "POST"

def __init__(self, data):
self.body = json.dumps(data).encode('utf-8')


class RuntimeMock:
last_call = None

def publish(self, block, event_type, data):
self.last_call = (block, event_type, data)


class XBlockMock:
def __init__(self):
self.runtime = RuntimeMock()


class ObjectUnderTest(XBlockMock, PublishEventMixin):
pass


class TestPublishEventMixin(unittest.TestCase):
def assert_no_calls_made(self, block):
self.assertFalse(block.last_call)

def assert_success(self, response):
self.assertEqual(json.loads(response.body)['result'], 'success')

def assert_error(self, response):
self.assertEqual(json.loads(response.body)['result'], 'error')

def test_error_when_no_event_type(self):
block = ObjectUnderTest()

response = block.publish_event(RequestMock({}))

self.assert_error(response)
self.assert_no_calls_made(block.runtime)

def test_uncustomized_publish_event(self):
block = ObjectUnderTest()

event_data = {"one": 1, "two": 2, "bool": True}
data = dict(event_data)
data["event_type"] = "test.event.uncustomized"

response = block.publish_event(RequestMock(data))

self.assert_success(response)
self.assertEqual(block.runtime.last_call, (block, "test.event.uncustomized", event_data))

def test_publish_event_with_additional_data(self):
block = ObjectUnderTest()
block.additional_publish_event_data = {"always_present": True, "block_id": "the-block-id"}

event_data = {"foo": True, "bar": False, "baz": None}
data = dict(event_data)
data["event_type"] = "test.event.customized"

response = block.publish_event(RequestMock(data))

expected_data = dict(event_data)
expected_data.update(block.additional_publish_event_data)

self.assert_success(response)
self.assertEqual(block.runtime.last_call, (block, "test.event.customized", expected_data))

def test_publish_event_fails_with_duplicate_data(self):
block = ObjectUnderTest()
block.additional_publish_event_data = {"good_argument": True, "clashing_argument": True}

event_data = {"fine_argument": True, "clashing_argument": False}
data = dict(event_data)
data["event_type"] = "test.event.clashing"

response = block.publish_event(RequestMock(data))

self.assert_error(response)
self.assert_no_calls_made(block.runtime)
Loading

0 comments on commit 76eaea3

Please sign in to comment.