Skip to content

Commit

Permalink
Add "item" field to item.dropped event
Browse files Browse the repository at this point in the history
Adds a new "item" field to the edx.drag_and_drop_v2.item.dropped event
data. It contains the item text label when present, otherwise the item
imageURL.

This makes it consistent with the location field, which contains the
zone text label.
  • Loading branch information
arbrandes committed Sep 2, 2016
1 parent f435c5b commit c78dbc6
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 32 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,10 @@ Example ("common" fields that are not interesting in this context have been left
...
"event": {
"is_correct": true, -- Whether the draggable item has been placed in the correct location.
"item": "Goes to the top", -- Name, or in the absence thereof, image URL of the draggable item.
"item_id": 0, -- ID of the draggable item.
"location": "The Top Zone", -- Name of the location the item was dragged to.
"location_id": 1, -- ID of the location the item was dragged to.
},
"event_source": "server", -- Common field, contains event source.
"event_type": "edx.drag_and_drop_v2.dropped", -- Common field, contains event name.
Expand All @@ -316,6 +318,8 @@ Real event example (taken from a devstack):
"event": {
"is_correct": true,
"location": "The Top Zone",
"location_id": 1,
"item": "Goes to the top",
"item_id": 0,
},
"event_source": "server",
Expand Down
16 changes: 11 additions & 5 deletions drag_and_drop_v2/default_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
ITEM_NO_ZONE_FEEDBACK = _("You silly, there are no zones for this one.")
ITEM_ANY_ZONE_FEEDBACK = _("Of course it goes here! It goes anywhere!")

ITEM_TOP_ZONE_NAME = _("Goes to the top")
ITEM_MIDDLE_ZONE_NAME = _("Goes to the middle")
ITEM_BOTTOM_ZONE_NAME = _("Goes to the bottom")
ITEM_ANY_ZONE_NAME = _("Goes anywhere")
ITEM_NO_ZONE_NAME = _("I don't belong anywhere")

START_FEEDBACK = _("Drag the items onto the image above.")
FINISH_FEEDBACK = _("Good work! You have completed this drag and drop problem.")

Expand Down Expand Up @@ -63,7 +69,7 @@
],
"items": [
{
"displayName": _("Goes to the top"),
"displayName": ITEM_TOP_ZONE_NAME,
"feedback": {
"incorrect": ITEM_INCORRECT_FEEDBACK,
"correct": ITEM_CORRECT_FEEDBACK.format(zone=TOP_ZONE_TITLE)
Expand All @@ -75,7 +81,7 @@
"id": 0,
},
{
"displayName": _("Goes to the middle"),
"displayName": ITEM_MIDDLE_ZONE_NAME,
"feedback": {
"incorrect": ITEM_INCORRECT_FEEDBACK,
"correct": ITEM_CORRECT_FEEDBACK.format(zone=MIDDLE_ZONE_TITLE)
Expand All @@ -87,7 +93,7 @@
"id": 1,
},
{
"displayName": _("Goes to the bottom"),
"displayName": ITEM_BOTTOM_ZONE_NAME,
"feedback": {
"incorrect": ITEM_INCORRECT_FEEDBACK,
"correct": ITEM_CORRECT_FEEDBACK.format(zone=BOTTOM_ZONE_TITLE)
Expand All @@ -99,7 +105,7 @@
"id": 2,
},
{
"displayName": _("Goes anywhere"),
"displayName": ITEM_ANY_ZONE_NAME,
"feedback": {
"incorrect": "",
"correct": ITEM_ANY_ZONE_FEEDBACK
Expand All @@ -113,7 +119,7 @@
"id": 3
},
{
"displayName": _("I don't belong anywhere"),
"displayName": ITEM_NO_ZONE_NAME,
"feedback": {
"incorrect": ITEM_NO_ZONE_FEEDBACK,
"correct": ""
Expand Down
5 changes: 5 additions & 0 deletions drag_and_drop_v2/drag_and_drop_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,12 @@ def _publish_item_dropped_event(self, attempt, is_correct):
# attempt should already be validated here - not doing the check for existing zone again
zone = self._get_zone_by_uid(attempt['zone'])

item_label = item.get("displayName")
if not item_label:
item_label = item.get("imageURL")

self.runtime.publish(self, 'edx.drag_and_drop_v2.item.dropped', {
'item': item_label,
'item_id': item['id'],
'location': zone.get("title"),
'location_id': zone.get("uid"),
Expand Down
49 changes: 49 additions & 0 deletions tests/integration/data/test_item_dropped.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"zones": [
{
"width": 200,
"title": "Zone 1",
"height": 100,
"y": "200",
"x": "100",
"uid": "zone-1"
},
{
"width": 200,
"title": "Zone 2",
"height": 100,
"y": 0,
"x": 0,
"uid": "zone-2"
}
],
"items": [
{
"displayName": "Has name",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zones": [
"zone-1"
],
"imageURL": "",
"id": 0
},
{
"displayName": "",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "zone-2",
"imageURL": "https://placehold.it/100x100",
"id": 1
}
],
"feedback": {
"start": "Start feedback",
"finish": "Finish feedback"
}
}

32 changes: 20 additions & 12 deletions tests/integration/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from collections import namedtuple

from bok_choy.promise import EmptyPromise
from workbench import scenarios
Expand All @@ -20,6 +21,8 @@
DEFAULT_DATA, START_FEEDBACK, FINISH_FEEDBACK,
TOP_ZONE_ID, TOP_ZONE_TITLE, MIDDLE_ZONE_ID, MIDDLE_ZONE_TITLE, BOTTOM_ZONE_ID, BOTTOM_ZONE_TITLE,
ITEM_CORRECT_FEEDBACK, ITEM_INCORRECT_FEEDBACK, ITEM_ANY_ZONE_FEEDBACK, ITEM_NO_ZONE_FEEDBACK,
ITEM_TOP_ZONE_NAME, ITEM_MIDDLE_ZONE_NAME, ITEM_BOTTOM_ZONE_NAME,
ITEM_ANY_ZONE_NAME, ITEM_NO_ZONE_NAME,
)

# Globals ###########################################################
Expand All @@ -29,13 +32,18 @@

# Classes ###########################################################

class ItemDefinition(object):
def __init__(self, item_id, zone_ids, zone_title, feedback_positive, feedback_negative):
self.feedback_negative = feedback_negative
self.feedback_positive = feedback_positive
self.zone_ids = zone_ids
self.zone_title = zone_title
self.item_id = item_id
ItemDefinition = namedtuple( # pylint: disable=invalid-name
"ItemDefinition",
[
"item_id",
"item_name",
"image_url",
"zone_ids",
"zone_title",
"feedback_positive",
"feedback_negative",
]
)


class BaseIntegrationTest(SeleniumBaseTest):
Expand Down Expand Up @@ -180,22 +188,22 @@ class DefaultDataTestMixin(object):

items_map = {
0: ItemDefinition(
0, [TOP_ZONE_ID], TOP_ZONE_TITLE,
0, ITEM_TOP_ZONE_NAME, "", [TOP_ZONE_ID], TOP_ZONE_TITLE,
ITEM_CORRECT_FEEDBACK.format(zone=TOP_ZONE_TITLE), ITEM_INCORRECT_FEEDBACK
),
1: ItemDefinition(
1, [MIDDLE_ZONE_ID], MIDDLE_ZONE_TITLE,
1, ITEM_MIDDLE_ZONE_NAME, "", [MIDDLE_ZONE_ID], MIDDLE_ZONE_TITLE,
ITEM_CORRECT_FEEDBACK.format(zone=MIDDLE_ZONE_TITLE), ITEM_INCORRECT_FEEDBACK
),
2: ItemDefinition(
2, [BOTTOM_ZONE_ID], BOTTOM_ZONE_TITLE,
2, ITEM_BOTTOM_ZONE_NAME, "", [BOTTOM_ZONE_ID], BOTTOM_ZONE_TITLE,
ITEM_CORRECT_FEEDBACK.format(zone=BOTTOM_ZONE_TITLE), ITEM_INCORRECT_FEEDBACK
),
3: ItemDefinition(
3, [MIDDLE_ZONE_ID, TOP_ZONE_ID, BOTTOM_ZONE_ID], MIDDLE_ZONE_TITLE,
3, ITEM_ANY_ZONE_NAME, "", [MIDDLE_ZONE_ID, TOP_ZONE_ID, BOTTOM_ZONE_ID], MIDDLE_ZONE_TITLE,
ITEM_ANY_ZONE_FEEDBACK, ITEM_INCORRECT_FEEDBACK
),
4: ItemDefinition(4, [], None, "", ITEM_NO_ZONE_FEEDBACK),
4: ItemDefinition(4, ITEM_NO_ZONE_NAME, "", [], None, "", ITEM_NO_ZONE_FEEDBACK),
}

all_zones = [
Expand Down
76 changes: 74 additions & 2 deletions tests/integration/test_events.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from ddt import data, ddt, unpack
from mock import Mock, patch
from selenium.webdriver.common.keys import Keys

from workbench.runtime import WorkbenchRuntime

from drag_and_drop_v2.default_data import (
TOP_ZONE_TITLE, TOP_ZONE_ID, MIDDLE_ZONE_TITLE, MIDDLE_ZONE_ID, ITEM_CORRECT_FEEDBACK, ITEM_INCORRECT_FEEDBACK
TOP_ZONE_TITLE, TOP_ZONE_ID, MIDDLE_ZONE_TITLE, MIDDLE_ZONE_ID, ITEM_CORRECT_FEEDBACK, ITEM_INCORRECT_FEEDBACK,
ITEM_TOP_ZONE_NAME, ITEM_MIDDLE_ZONE_NAME,
)
from tests.integration.test_base import BaseIntegrationTest, DefaultDataTestMixin, InteractionTestBase
from tests.integration.test_base import BaseIntegrationTest, DefaultDataTestMixin, InteractionTestBase, ItemDefinition
from tests.integration.test_interaction import DefaultDataTestMixin, ParameterizedTestsMixin
from tests.integration.test_interaction_assessment import DefaultAssessmentDataTestMixin, AssessmentTestMixin

Expand Down Expand Up @@ -44,6 +46,7 @@ class EventsFiredTest(DefaultDataTestMixin, ParameterizedTestsMixin, BaseEventsT
'name': 'edx.drag_and_drop_v2.item.dropped',
'data': {
'is_correct': True,
'item': ITEM_TOP_ZONE_NAME,
'item_id': 0,
'location': TOP_ZONE_TITLE,
'location_id': TOP_ZONE_ID,
Expand Down Expand Up @@ -95,6 +98,7 @@ class AssessmentEventsFiredTest(
'name': 'edx.drag_and_drop_v2.item.dropped',
'data': {
'is_correct': False,
'item': ITEM_TOP_ZONE_NAME,
'item_id': 0,
'location': MIDDLE_ZONE_TITLE,
'location_id': MIDDLE_ZONE_ID,
Expand All @@ -108,6 +112,7 @@ class AssessmentEventsFiredTest(
'name': 'edx.drag_and_drop_v2.item.dropped',
'data': {
'is_correct': False,
'item': ITEM_MIDDLE_ZONE_NAME,
'item_id': 1,
'location': TOP_ZONE_TITLE,
'location_id': TOP_ZONE_ID,
Expand Down Expand Up @@ -140,3 +145,70 @@ def test_event(self):
dummy, name, published_data = self.publish.call_args_list[index][0]
self.assertEqual(name, event['name'])
self.assertEqual(published_data, event['data'])


@ddt
class ItemDroppedEventTest(DefaultDataTestMixin, BaseEventsTests):
"""
Test that the item.dropped event behaves properly.
"""
items_map = {
0: ItemDefinition(0, "Has name", "", 'zone-1', "Zone 1", "Yes", "No"),
1: ItemDefinition(1, "", "https://placehold.it/100x100", 'zone-2', "Zone 2", "Yes", "No"),
}

scenarios = (
(
['zone-1', 'zone-2'],
[
{
'is_correct': True,
'item': "Has name",
'item_id': 0,
'location': 'Zone 1',
'location_id': 'zone-1'
},
{
'is_correct': True,
'item': "https://placehold.it/100x100",
'item_id': 1,
'location': 'Zone 2',
'location_id': 'zone-2'
}
],
),
(
['zone-2', 'zone-1'],
[
{
'is_correct': False,
'item': "Has name",
'item_id': 0,
'location': 'Zone 2',
'location_id': 'zone-2'
},
{
'is_correct': False,
'item': "https://placehold.it/100x100",
'item_id': 1,
'location': 'Zone 1',
'location_id': 'zone-1'
}
],
),
)

def _get_scenario_xml(self):
return self._get_custom_scenario_xml("data/test_item_dropped.json")

@data(*scenarios) # pylint: disable=star-args
@unpack
def test_item_dropped_event(self, placement, expected_events):
for i, zone in enumerate(placement):
self.place_item(i, zone, Keys.RETURN)

events = self.publish.call_args_list
event_name = 'edx.drag_and_drop_v2.item.dropped'
published_events = [event[0][2] for event in events if event[0][1] == event_name]
self.assertEqual(published_events, expected_events)
34 changes: 21 additions & 13 deletions tests/integration/test_interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,15 @@ def test_keyboard_help(self, use_keyboard):
class MultipleValidOptionsInteractionTest(DefaultDataTestMixin, InteractionTestBase, BaseIntegrationTest):

items_map = {
0: ItemDefinition(0, ['zone-1', 'zone-2'], ["Zone 1", "Zone 2"], ["Yes 1", "Yes 1"], ["No 1", "No 1"]),
0: ItemDefinition(
0,
"Item",
"",
['zone-1', 'zone-2'],
["Zone 1", "Zone 2"],
["Yes 1", "Yes 1"],
["No 1", "No 1"]
),
}

def test_multiple_positive_feedback(self):
Expand Down Expand Up @@ -334,9 +342,9 @@ def test_space_bar_scroll(self):

class CustomDataInteractionTest(StandardInteractionTest):
items_map = {
0: ItemDefinition(0, ['zone-1'], "Zone 1", "Yes 1", "No 1"),
1: ItemDefinition(1, ['zone-2'], "Zone 2", "Yes 2", "No 2"),
2: ItemDefinition(2, [], None, "", "No Zone for this")
0: ItemDefinition(0, "Item 0", "", ['zone-1'], "Zone 1", "Yes 1", "No 1"),
1: ItemDefinition(1, "Item 1", "", ['zone-2'], "Zone 2", "Yes 2", "No 2"),
2: ItemDefinition(2, "Item 2", "", [], None, "", "No Zone for this")
}

all_zones = [('zone-1', 'Zone 1'), ('zone-2', 'Zone 2')]
Expand All @@ -352,9 +360,9 @@ def _get_scenario_xml(self):

class CustomHtmlDataInteractionTest(StandardInteractionTest):
items_map = {
0: ItemDefinition(0, ['zone-1'], 'Zone <i>1</i>', "Yes <b>1</b>", "No <b>1</b>"),
1: ItemDefinition(1, ['zone-2'], 'Zone <b>2</b>', "Yes <i>2</i>", "No <i>2</i>"),
2: ItemDefinition(2, [], None, "", "No Zone for <i>X</i>")
0: ItemDefinition(0, "Item 0", "", ['zone-1'], 'Zone <i>1</i>', "Yes <b>1</b>", "No <b>1</b>"),
1: ItemDefinition(1, "Item 1", "", ['zone-2'], 'Zone <b>2</b>', "Yes <i>2</i>", "No <i>2</i>"),
2: ItemDefinition(2, "Item 2", "", [], None, "", "No Zone for <i>X</i>")
}

all_zones = [('zone-1', 'Zone 1'), ('zone-2', 'Zone 2')]
Expand All @@ -377,14 +385,14 @@ class MultipleBlocksDataInteraction(ParameterizedTestsMixin, InteractionTestBase

item_maps = {
'block1': {
0: ItemDefinition(0, ['zone-1'], 'Zone 1', "Yes 1", "No 1"),
1: ItemDefinition(1, ['zone-2'], 'Zone 2', "Yes 2", "No 2"),
2: ItemDefinition(2, [], None, "", "No Zone for this")
0: ItemDefinition(0, "Item 0", "", ['zone-1'], 'Zone 1', "Yes 1", "No 1"),
1: ItemDefinition(1, "Item 1", "", ['zone-2'], 'Zone 2', "Yes 2", "No 2"),
2: ItemDefinition(2, "Item 2", "", [], None, "", "No Zone for this")
},
'block2': {
10: ItemDefinition(10, ['zone-51'], 'Zone 51', "Correct 1", "Incorrect 1"),
20: ItemDefinition(20, ['zone-52'], 'Zone 52', "Correct 2", "Incorrect 2"),
30: ItemDefinition(30, [], None, "", "No Zone for this")
10: ItemDefinition(10, "Item 10", "", ['zone-51'], 'Zone 51', "Correct 1", "Incorrect 1"),
20: ItemDefinition(20, "Item 20", "", ['zone-52'], 'Zone 52', "Correct 2", "Incorrect 2"),
30: ItemDefinition(30, "Item 30", "", [], None, "", "No Zone for this")
},
}

Expand Down

0 comments on commit c78dbc6

Please sign in to comment.