Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
xblock-drag-and-drop-v2
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
xblock-drag-and-drop-v2
Commits
de47111b
Commit
de47111b
authored
Aug 10, 2016
by
rowan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Changes to usability for keyboard controls (SOL-1807)
parent
ef6d2e26
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
92 additions
and
20 deletions
+92
-20
drag_and_drop_v2/public/js/drag_and_drop.js
+30
-7
tests/integration/test_interaction.py
+60
-12
tests/integration/test_render.py
+2
-1
No files found.
drag_and_drop_v2/public/js/drag_and_drop.js
View file @
de47111b
...
@@ -116,10 +116,10 @@ function DragAndDropTemplates(configuration) {
...
@@ -116,10 +116,10 @@ function DragAndDropTemplates(configuration) {
itemSpinnerTemplate
(
item
)
itemSpinnerTemplate
(
item
)
];
];
var
item_content
=
itemContentTemplate
(
item
);
var
item_content
=
itemContentTemplate
(
item
);
if
(
item
.
is_placed
)
{
// Insert information about zone in which this item has been placed
// Insert information about zone in which this item has been placed
var
item_description_id
=
configuration
.
url_name
+
'-item-'
+
item
.
value
+
'-description'
;
var
item_description_id
=
configuration
.
url_name
+
'-item-'
+
item
.
value
+
'-description'
;
item_content
.
properties
.
attributes
=
{
'aria-describedby'
:
item_description_id
};
item_content
.
properties
.
attributes
=
{
'aria-describedby'
:
item_description_id
};
if
(
item
.
is_placed
)
{
var
zone_title
=
(
zone
.
title
||
"Unknown Zone"
);
// This "Unknown" text should never be seen, so does not need i18n
var
zone_title
=
(
zone
.
title
||
"Unknown Zone"
);
// This "Unknown" text should never be seen, so does not need i18n
var
description_content
;
var
description_content
;
if
(
configuration
.
mode
===
DragAndDropBlock
.
ASSESSMENT_MODE
)
{
if
(
configuration
.
mode
===
DragAndDropBlock
.
ASSESSMENT_MODE
)
{
...
@@ -135,8 +135,14 @@ function DragAndDropTemplates(configuration) {
...
@@ -135,8 +135,14 @@ function DragAndDropTemplates(configuration) {
{
key
:
item
.
value
+
'-description'
,
id
:
item_description_id
,
className
:
'sr'
},
{
key
:
item
.
value
+
'-description'
,
id
:
item_description_id
,
className
:
'sr'
},
description_content
description_content
);
);
children
.
splice
(
1
,
0
,
item_description
);
}
else
{
var
item_description
=
h
(
'div'
,
{
id
:
item_description_id
,
className
:
'sr'
},
gettext
(
'Press "Enter", "Space", "Ctrl-m", or "⌘-m" on an item to select it for dropping, then navigate to the zone you want to drop it on.'
)
);
}
}
children
.
splice
(
1
,
0
,
item_description
);
children
.
splice
(
1
,
0
,
item_content
);
children
.
splice
(
1
,
0
,
item_content
);
return
(
return
(
h
(
h
(
...
@@ -190,13 +196,28 @@ function DragAndDropTemplates(configuration) {
...
@@ -190,13 +196,28 @@ function DragAndDropTemplates(configuration) {
// If zone is aligned, mark its item alignment
// If zone is aligned, mark its item alignment
// and render its placed items as children
// and render its placed items as children
var
item_wrapper
=
'div.item-wrapper'
;
var
item_wrapper
=
'div.item-wrapper'
;
var
items_in_zone
=
[];
var
is_item_in_zone
=
function
(
i
)
{
return
i
.
is_placed
&&
(
i
.
zone
===
zone
.
uid
);
};
var
items_in_zone
=
$
.
grep
(
ctx
.
items
,
is_item_in_zone
);
var
zone_description_id
=
'zone-'
+
zone
.
id
+
'-description'
;
if
(
items_in_zone
.
length
==
0
)
{
var
zone_description
=
h
(
'div'
,
{
id
:
zone_description_id
,
className
:
'sr'
},
gettext
(
"No items placed here"
)
);
}
else
{
var
zone_description
=
h
(
'div'
,
{
id
:
zone_description_id
,
className
:
'sr'
},
gettext
(
'Items placed here: '
)
+
items_in_zone
.
map
(
function
(
item
)
{
return
item
.
displayName
;
}).
join
(
", "
)
);
}
if
(
zone
.
align
!==
'none'
)
{
if
(
zone
.
align
!==
'none'
)
{
item_wrapper
+=
'.item-align.item-align-'
+
zone
.
align
;
item_wrapper
+=
'.item-align.item-align-'
+
zone
.
align
;
var
is_item_in_zone
=
function
(
i
)
{
return
i
.
is_placed
&&
(
i
.
zone
===
zone
.
uid
);
};
//items_in_zone = $.grep(ctx.items, is_item_in_zone);
items_in_zone
=
$
.
grep
(
ctx
.
items
,
is_item_in_zone
);
}
else
{
items_in_zone
=
[];
}
}
return
(
return
(
h
(
h
(
selector
,
selector
,
...
@@ -211,6 +232,7 @@ function DragAndDropTemplates(configuration) {
...
@@ -211,6 +232,7 @@ function DragAndDropTemplates(configuration) {
'data-zone_id'
:
zone
.
id
,
'data-zone_id'
:
zone
.
id
,
'data-zone_align'
:
zone
.
align
,
'data-zone_align'
:
zone
.
align
,
'role'
:
'button'
,
'role'
:
'button'
,
'aria-describedby'
:
zone_description_id
,
},
},
style
:
{
style
:
{
top
:
zone
.
y_percent
+
'%'
,
left
:
zone
.
x_percent
+
"%"
,
top
:
zone
.
y_percent
+
'%'
,
left
:
zone
.
x_percent
+
"%"
,
...
@@ -220,7 +242,8 @@ function DragAndDropTemplates(configuration) {
...
@@ -220,7 +242,8 @@ function DragAndDropTemplates(configuration) {
[
[
h
(
'p'
,
{
className
:
className
},
zone
.
title
),
h
(
'p'
,
{
className
:
className
},
zone
.
title
),
h
(
'p'
,
{
className
:
'zone-description sr'
},
zone
.
description
),
h
(
'p'
,
{
className
:
'zone-description sr'
},
zone
.
description
),
h
(
item_wrapper
,
renderCollection
(
itemTemplate
,
items_in_zone
,
ctx
))
h
(
item_wrapper
,
renderCollection
(
itemTemplate
,
items_in_zone
,
ctx
)),
zone_description
]
]
)
)
);
);
...
...
tests/integration/test_interaction.py
View file @
de47111b
# pylint: disable=too-many-lines
# -*- coding: utf-8 -*-
# Imports ###########################################################
# Imports ###########################################################
from
ddt
import
ddt
,
data
,
unpack
from
ddt
import
ddt
,
data
,
unpack
from
mock
import
Mock
,
patch
from
mock
import
Mock
,
patch
from
selenium.common.exceptions
import
NoSuchElementException
,
WebDriverException
from
selenium.common.exceptions
import
WebDriverException
from
selenium.webdriver
import
ActionChains
from
selenium.webdriver
import
ActionChains
from
selenium.webdriver.common.keys
import
Keys
from
selenium.webdriver.common.keys
import
Keys
from
selenium.webdriver.support.ui
import
WebDriverWait
from
selenium.webdriver.support.ui
import
WebDriverWait
...
@@ -19,15 +22,14 @@ from drag_and_drop_v2.default_data import (
...
@@ -19,15 +22,14 @@ from drag_and_drop_v2.default_data import (
)
)
from
drag_and_drop_v2.utils
import
FeedbackMessages
from
drag_and_drop_v2.utils
import
FeedbackMessages
from
.test_base
import
BaseIntegrationTest
from
.test_base
import
BaseIntegrationTest
import
time
# Globals ###########################################################
# Globals ###########################################################
loader
=
ResourceLoader
(
__name__
)
loader
=
ResourceLoader
(
__name__
)
# Classes ###########################################################
# Classes ###########################################################
class
ItemDefinition
(
object
):
class
ItemDefinition
(
object
):
def
__init__
(
self
,
item_id
,
zone_ids
,
zone_title
,
feedback_positive
,
feedback_negative
):
def
__init__
(
self
,
item_id
,
zone_ids
,
zone_title
,
feedback_positive
,
feedback_negative
):
self
.
feedback_negative
=
feedback_negative
self
.
feedback_negative
=
feedback_negative
...
@@ -52,12 +54,21 @@ class InteractionTestBase(object):
...
@@ -52,12 +54,21 @@ class InteractionTestBase(object):
if
definition
.
zone_ids
==
[]
if
definition
.
zone_ids
==
[]
}
}
@classmethod
def
_get_items_by_zone
(
cls
,
items_map
):
zone_ids
=
set
([
definition
.
zone_ids
[
0
]
for
_
,
definition
in
items_map
.
items
()
if
definition
.
zone_ids
])
return
{
zone_id
:
{
item_key
:
definition
for
item_key
,
definition
in
items_map
.
items
()
if
definition
.
zone_ids
and
definition
.
zone_ids
[
0
]
is
zone_id
}
for
zone_id
in
zone_ids
}
def
setUp
(
self
):
def
setUp
(
self
):
super
(
InteractionTestBase
,
self
)
.
setUp
()
super
(
InteractionTestBase
,
self
)
.
setUp
()
scenario_xml
=
self
.
_get_scenario_xml
()
scenario_xml
=
self
.
_get_scenario_xml
()
self
.
_add_scenario
(
self
.
PAGE_ID
,
self
.
PAGE_TITLE
,
scenario_xml
)
self
.
_add_scenario
(
self
.
PAGE_ID
,
self
.
PAGE_TITLE
,
scenario_xml
)
time
.
sleep
(
2
)
self
.
_page
=
self
.
go_to_page
(
self
.
PAGE_TITLE
)
self
.
_page
=
self
.
go_to_page
(
self
.
PAGE_TITLE
)
# Resize window so that the entire drag container is visible.
# Resize window so that the entire drag container is visible.
# Selenium has issues when dragging to an area that is off screen.
# Selenium has issues when dragging to an area that is off screen.
...
@@ -214,14 +225,12 @@ class InteractionTestBase(object):
...
@@ -214,14 +225,12 @@ class InteractionTestBase(object):
self
.
assertEqual
(
item
.
get_attribute
(
'class'
),
'option'
)
self
.
assertEqual
(
item
.
get_attribute
(
'class'
),
'option'
)
self
.
assertEqual
(
item
.
get_attribute
(
'tabindex'
),
'0'
)
self
.
assertEqual
(
item
.
get_attribute
(
'tabindex'
),
'0'
)
self
.
assertEqual
(
item
.
get_attribute
(
'aria-grabbed'
),
'false'
)
self
.
assertEqual
(
item
.
get_attribute
(
'aria-grabbed'
),
'false'
)
self
.
assertIsNone
(
item_content
.
get_attribute
(
'aria-describedby'
))
item_description_id
=
'-item-{}-description'
.
format
(
item_value
)
self
.
assertEqual
(
item_content
.
get_attribute
(
'aria-describedby'
),
item_description_id
)
try
:
describedby_text
=
(
u'Press "Enter", "Space", "Ctrl-m", or "⌘-m" on an item to select it for dropping, '
item
.
find_element_by_css_selector
(
'.sr'
)
'then navigate to the zone you want to drop it on.'
)
except
NoSuchElementException
:
self
.
assertEqual
(
item
.
find_element_by_css_selector
(
'.sr'
)
.
text
,
describedby_text
)
pass
else
:
self
.
fail
(
'Reverted item should not have .sr description.'
)
def
place_decoy_items
(
self
,
items_map
,
action_key
):
def
place_decoy_items
(
self
,
items_map
,
action_key
):
decoy_items
=
self
.
_get_items_without_zone
(
items_map
)
decoy_items
=
self
.
_get_items_without_zone
(
items_map
)
...
@@ -520,6 +529,45 @@ class StandardInteractionTest(DefaultDataTestMixin, InteractionTestBase, BaseInt
...
@@ -520,6 +529,45 @@ class StandardInteractionTest(DefaultDataTestMixin, InteractionTestBase, BaseInt
self
.
items_map
,
self
.
all_zones
,
action_key
=
action_key
self
.
items_map
,
self
.
all_zones
,
action_key
=
action_key
)
)
def
test_alt_text_image
(
self
):
target_img
=
self
.
_page
.
find_element_by_css_selector
(
'.target-img'
)
alt_text
=
target_img
.
get_attribute
(
'alt'
)
items_container
=
self
.
_page
.
find_element_by_css_selector
(
'.target'
)
if
items_container
.
get_attribute
(
'aria-describedby'
):
self
.
assertEqual
(
items_container
.
get_attribute
(
'aria-describedby'
)
.
text
,
alt_text
)
def
test_alt_text_keyboard_help_over_item
(
self
):
for
_
,
definition
in
self
.
items_map
.
items
():
item
=
self
.
_get_unplaced_item_by_value
(
definition
.
item_id
)
ActionChains
(
self
.
browser
)
.
move_to_element
(
item
)
.
perform
()
keyboard_help_text
=
(
u'Press "Enter", "Space", "Ctrl-m", or "⌘-m" on an item to select it for dropping, '
'then navigate to the zone you want to drop it on.'
)
self
.
assertEqual
(
item
.
find_element_by_css_selector
(
'.sr'
)
.
text
,
keyboard_help_text
)
def
test_alt_text_for_zones
(
self
):
self
.
_get_popup
()
self
.
_get_popup_content
()
self
.
scroll_down
(
pixels
=
100
)
# Place all items in zones where they belong
for
definition
in
self
.
_get_items_with_zone
(
self
.
items_map
)
.
values
():
self
.
place_item
(
definition
.
item_id
,
definition
.
zone_ids
[
0
])
# Check if alt text appears for that item when the user tabs over the zone
for
zone_id
,
items_dict
in
self
.
_get_items_by_zone
(
self
.
items_map
)
.
items
():
if
zone_id
is
None
:
continue
zone
=
self
.
_get_zone_by_id
(
zone_id
)
zone_description
=
zone
.
find_element_by_id
(
zone
.
get_attribute
(
'aria-describedby'
))
.
text
# Iterate over all items placed in that zone and save a list of their descriptions
for
_
,
definition
in
items_dict
.
items
():
item
=
self
.
_get_placed_item_by_value
(
definition
.
item_id
)
self
.
wait_until_visible
(
item
)
item_content
=
item
.
find_element_by_css_selector
(
'.item-content'
)
self
.
wait_until_visible
(
item_content
)
self
.
assertTrue
(
item_content
.
text
in
zone_description
)
@data
(
None
,
Keys
.
RETURN
,
Keys
.
SPACE
,
Keys
.
CONTROL
+
'm'
,
Keys
.
COMMAND
+
'm'
)
@data
(
None
,
Keys
.
RETURN
,
Keys
.
SPACE
,
Keys
.
CONTROL
+
'm'
,
Keys
.
COMMAND
+
'm'
)
def
test_final_feedback_and_reset
(
self
,
action_key
):
def
test_final_feedback_and_reset
(
self
,
action_key
):
self
.
parameterized_final_feedback_and_reset
(
self
.
items_map
,
self
.
feedback
,
action_key
=
action_key
)
self
.
parameterized_final_feedback_and_reset
(
self
.
items_map
,
self
.
feedback
,
action_key
=
action_key
)
...
...
tests/integration/test_render.py
View file @
de47111b
...
@@ -150,7 +150,8 @@ class TestDragAndDropRender(BaseIntegrationTest):
...
@@ -150,7 +150,8 @@ class TestDragAndDropRender(BaseIntegrationTest):
try
:
try
:
background_image
=
item
.
find_element_by_css_selector
(
'img'
)
background_image
=
item
.
find_element_by_css_selector
(
'img'
)
except
NoSuchElementException
:
except
NoSuchElementException
:
self
.
assertEqual
(
item
.
text
,
self
.
ITEM_PROPERTIES
[
index
][
'text'
])
item_content
=
item
.
find_element_by_css_selector
(
'.item-content'
)
self
.
assertEqual
(
item_content
.
text
,
self
.
ITEM_PROPERTIES
[
index
][
'text'
])
else
:
else
:
self
.
assertEqual
(
self
.
assertEqual
(
background_image
.
get_attribute
(
'alt'
),
background_image
.
get_attribute
(
'alt'
),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment