523 lines
18 KiB
Python
523 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from odoo.tests.common import TransactionCase
|
|
from hypothesis import given, strategies as st, settings
|
|
|
|
|
|
class TestKeyboardNavigation(TransactionCase):
|
|
"""
|
|
Test cases for keyboard navigation behavior
|
|
|
|
Property 20: Keyboard navigation enables star selection
|
|
For any star in the rating form, it should be selectable using
|
|
keyboard navigation (arrow keys and Enter).
|
|
|
|
Validates: Requirements 8.2
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(TestKeyboardNavigation, self).setUp()
|
|
# We'll test the keyboard navigation logic that would be used in the frontend
|
|
# The logic is: arrow keys change selection, Enter confirms
|
|
self.max_stars = 5
|
|
self.min_stars = 1
|
|
|
|
def _simulate_arrow_right(self, current_value):
|
|
"""
|
|
Simulate pressing the ArrowRight key.
|
|
|
|
This mirrors the logic in rating_stars.js onKeyDown():
|
|
- ArrowRight increases rating by 1
|
|
- Maximum value is maxStars (5)
|
|
|
|
Args:
|
|
current_value: The current selected value (0-5)
|
|
|
|
Returns:
|
|
The new selected value after pressing ArrowRight
|
|
"""
|
|
if current_value < self.max_stars:
|
|
return current_value + 1
|
|
return current_value
|
|
|
|
def _simulate_arrow_left(self, current_value):
|
|
"""
|
|
Simulate pressing the ArrowLeft key.
|
|
|
|
This mirrors the logic in rating_stars.js onKeyDown():
|
|
- ArrowLeft decreases rating by 1
|
|
- Minimum value is 1 (cannot go below 1)
|
|
|
|
Args:
|
|
current_value: The current selected value (0-5)
|
|
|
|
Returns:
|
|
The new selected value after pressing ArrowLeft
|
|
"""
|
|
if current_value > self.min_stars:
|
|
return current_value - 1
|
|
return current_value
|
|
|
|
def _simulate_arrow_up(self, current_value):
|
|
"""
|
|
Simulate pressing the ArrowUp key.
|
|
|
|
This mirrors the logic in rating_stars.js onKeyDown():
|
|
- ArrowUp increases rating by 1 (same as ArrowRight)
|
|
- Maximum value is maxStars (5)
|
|
|
|
Args:
|
|
current_value: The current selected value (0-5)
|
|
|
|
Returns:
|
|
The new selected value after pressing ArrowUp
|
|
"""
|
|
return self._simulate_arrow_right(current_value)
|
|
|
|
def _simulate_arrow_down(self, current_value):
|
|
"""
|
|
Simulate pressing the ArrowDown key.
|
|
|
|
This mirrors the logic in rating_stars.js onKeyDown():
|
|
- ArrowDown decreases rating by 1 (same as ArrowLeft)
|
|
- Minimum value is 1 (cannot go below 1)
|
|
|
|
Args:
|
|
current_value: The current selected value (0-5)
|
|
|
|
Returns:
|
|
The new selected value after pressing ArrowDown
|
|
"""
|
|
return self._simulate_arrow_left(current_value)
|
|
|
|
def _simulate_home_key(self):
|
|
"""
|
|
Simulate pressing the Home key.
|
|
|
|
This mirrors the logic in rating_stars.js onKeyDown():
|
|
- Home jumps to 1 star
|
|
|
|
Returns:
|
|
The new selected value (always 1)
|
|
"""
|
|
return 1
|
|
|
|
def _simulate_end_key(self):
|
|
"""
|
|
Simulate pressing the End key.
|
|
|
|
This mirrors the logic in rating_stars.js onKeyDown():
|
|
- End jumps to maxStars (5)
|
|
|
|
Returns:
|
|
The new selected value (always 5)
|
|
"""
|
|
return self.max_stars
|
|
|
|
def _verify_keyboard_navigation_property(self, initial_value, key_action):
|
|
"""
|
|
Verify that keyboard navigation enables star selection.
|
|
|
|
The property states: For any star in the rating form, it should be
|
|
selectable using keyboard navigation (arrow keys and Enter).
|
|
|
|
Args:
|
|
initial_value: The initial selected value (0-5)
|
|
key_action: The keyboard action to perform ('right', 'left', 'up', 'down', 'home', 'end')
|
|
"""
|
|
# Simulate the keyboard action
|
|
if key_action == 'right':
|
|
new_value = self._simulate_arrow_right(initial_value)
|
|
elif key_action == 'left':
|
|
new_value = self._simulate_arrow_left(initial_value)
|
|
elif key_action == 'up':
|
|
new_value = self._simulate_arrow_up(initial_value)
|
|
elif key_action == 'down':
|
|
new_value = self._simulate_arrow_down(initial_value)
|
|
elif key_action == 'home':
|
|
new_value = self._simulate_home_key()
|
|
elif key_action == 'end':
|
|
new_value = self._simulate_end_key()
|
|
else:
|
|
raise ValueError(f"Unknown key action: {key_action}")
|
|
|
|
# Property 1: New value should be within valid range
|
|
self.assertGreaterEqual(
|
|
new_value,
|
|
0,
|
|
f"After {key_action} from {initial_value}, value should be >= 0, got {new_value}"
|
|
)
|
|
self.assertLessEqual(
|
|
new_value,
|
|
self.max_stars,
|
|
f"After {key_action} from {initial_value}, value should be <= {self.max_stars}, got {new_value}"
|
|
)
|
|
|
|
# Property 2: Value should change appropriately based on key action
|
|
if key_action in ['right', 'up']:
|
|
if initial_value < self.max_stars:
|
|
self.assertEqual(
|
|
new_value,
|
|
initial_value + 1,
|
|
f"Arrow right/up from {initial_value} should increase to {initial_value + 1}"
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
new_value,
|
|
initial_value,
|
|
f"Arrow right/up from max value {initial_value} should stay at {initial_value}"
|
|
)
|
|
elif key_action in ['left', 'down']:
|
|
if initial_value > self.min_stars:
|
|
self.assertEqual(
|
|
new_value,
|
|
initial_value - 1,
|
|
f"Arrow left/down from {initial_value} should decrease to {initial_value - 1}"
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
new_value,
|
|
initial_value,
|
|
f"Arrow left/down from min value {initial_value} should stay at {initial_value}"
|
|
)
|
|
elif key_action == 'home':
|
|
self.assertEqual(
|
|
new_value,
|
|
1,
|
|
f"Home key should jump to 1 star"
|
|
)
|
|
elif key_action == 'end':
|
|
self.assertEqual(
|
|
new_value,
|
|
self.max_stars,
|
|
f"End key should jump to {self.max_stars} stars"
|
|
)
|
|
|
|
return new_value
|
|
|
|
# Feature: helpdesk-rating-five-stars, Property 20: Keyboard navigation enables star selection
|
|
@given(
|
|
initial_value=st.integers(min_value=0, max_value=5),
|
|
key_action=st.sampled_from(['right', 'left', 'up', 'down', 'home', 'end'])
|
|
)
|
|
@settings(max_examples=100, deadline=None)
|
|
def test_property_keyboard_navigation_enables_selection(self, initial_value, key_action):
|
|
"""
|
|
Property 20: Keyboard navigation enables star selection
|
|
|
|
For any initial rating value (0-5) and any keyboard action
|
|
(arrow keys, Home, End), the system should enable star selection
|
|
through keyboard navigation.
|
|
|
|
This tests that:
|
|
1. Arrow keys change the rating value appropriately
|
|
2. Home/End keys jump to min/max values
|
|
3. Values stay within valid range (1-5)
|
|
4. Keyboard navigation provides an alternative to mouse clicks
|
|
|
|
Validates: Requirements 8.2
|
|
"""
|
|
self._verify_keyboard_navigation_property(initial_value, key_action)
|
|
|
|
def test_keyboard_navigation_arrow_right(self):
|
|
"""
|
|
Test that ArrowRight increases rating by 1
|
|
"""
|
|
# Test from each possible value
|
|
for value in range(0, self.max_stars):
|
|
new_value = self._simulate_arrow_right(value)
|
|
if value < self.max_stars:
|
|
self.assertEqual(
|
|
new_value,
|
|
value + 1,
|
|
f"ArrowRight from {value} should increase to {value + 1}"
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
new_value,
|
|
value,
|
|
f"ArrowRight from max {value} should stay at {value}"
|
|
)
|
|
|
|
def test_keyboard_navigation_arrow_left(self):
|
|
"""
|
|
Test that ArrowLeft decreases rating by 1
|
|
"""
|
|
# Test from each possible value
|
|
for value in range(1, self.max_stars + 1):
|
|
new_value = self._simulate_arrow_left(value)
|
|
if value > self.min_stars:
|
|
self.assertEqual(
|
|
new_value,
|
|
value - 1,
|
|
f"ArrowLeft from {value} should decrease to {value - 1}"
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
new_value,
|
|
value,
|
|
f"ArrowLeft from min {value} should stay at {value}"
|
|
)
|
|
|
|
def test_keyboard_navigation_arrow_up(self):
|
|
"""
|
|
Test that ArrowUp increases rating by 1 (same as ArrowRight)
|
|
"""
|
|
for value in range(0, self.max_stars):
|
|
new_value = self._simulate_arrow_up(value)
|
|
if value < self.max_stars:
|
|
self.assertEqual(
|
|
new_value,
|
|
value + 1,
|
|
f"ArrowUp from {value} should increase to {value + 1}"
|
|
)
|
|
|
|
def test_keyboard_navigation_arrow_down(self):
|
|
"""
|
|
Test that ArrowDown decreases rating by 1 (same as ArrowLeft)
|
|
"""
|
|
for value in range(1, self.max_stars + 1):
|
|
new_value = self._simulate_arrow_down(value)
|
|
if value > self.min_stars:
|
|
self.assertEqual(
|
|
new_value,
|
|
value - 1,
|
|
f"ArrowDown from {value} should decrease to {value - 1}"
|
|
)
|
|
|
|
def test_keyboard_navigation_home_key(self):
|
|
"""
|
|
Test that Home key jumps to 1 star
|
|
"""
|
|
# From any value, Home should go to 1
|
|
for value in range(0, self.max_stars + 1):
|
|
new_value = self._simulate_home_key()
|
|
self.assertEqual(
|
|
new_value,
|
|
1,
|
|
f"Home key from {value} should jump to 1"
|
|
)
|
|
|
|
def test_keyboard_navigation_end_key(self):
|
|
"""
|
|
Test that End key jumps to 5 stars
|
|
"""
|
|
# From any value, End should go to maxStars
|
|
for value in range(0, self.max_stars + 1):
|
|
new_value = self._simulate_end_key()
|
|
self.assertEqual(
|
|
new_value,
|
|
self.max_stars,
|
|
f"End key from {value} should jump to {self.max_stars}"
|
|
)
|
|
|
|
def test_keyboard_navigation_boundary_cases(self):
|
|
"""
|
|
Test boundary cases for keyboard navigation
|
|
"""
|
|
# Test at minimum value (1)
|
|
new_value = self._simulate_arrow_left(1)
|
|
self.assertEqual(new_value, 1, "Cannot go below 1 with ArrowLeft")
|
|
|
|
new_value = self._simulate_arrow_down(1)
|
|
self.assertEqual(new_value, 1, "Cannot go below 1 with ArrowDown")
|
|
|
|
# Test at maximum value (5)
|
|
new_value = self._simulate_arrow_right(5)
|
|
self.assertEqual(new_value, 5, "Cannot go above 5 with ArrowRight")
|
|
|
|
new_value = self._simulate_arrow_up(5)
|
|
self.assertEqual(new_value, 5, "Cannot go above 5 with ArrowUp")
|
|
|
|
# Test at zero (edge case)
|
|
new_value = self._simulate_arrow_right(0)
|
|
self.assertEqual(new_value, 1, "ArrowRight from 0 should go to 1")
|
|
|
|
new_value = self._simulate_arrow_left(0)
|
|
self.assertEqual(new_value, 0, "ArrowLeft from 0 should stay at 0")
|
|
|
|
def test_keyboard_navigation_sequential_increase(self):
|
|
"""
|
|
Test sequential keyboard navigation from 0 to 5
|
|
"""
|
|
value = 0
|
|
|
|
# Press ArrowRight 5 times to go from 0 to 5
|
|
for expected in range(1, self.max_stars + 1):
|
|
value = self._simulate_arrow_right(value)
|
|
self.assertEqual(
|
|
value,
|
|
expected,
|
|
f"After {expected} ArrowRight presses, value should be {expected}"
|
|
)
|
|
|
|
# One more press should stay at 5
|
|
value = self._simulate_arrow_right(value)
|
|
self.assertEqual(value, 5, "Should stay at max value 5")
|
|
|
|
def test_keyboard_navigation_sequential_decrease(self):
|
|
"""
|
|
Test sequential keyboard navigation from 5 to 1
|
|
"""
|
|
value = 5
|
|
|
|
# Press ArrowLeft 4 times to go from 5 to 1
|
|
for expected in range(4, 0, -1):
|
|
value = self._simulate_arrow_left(value)
|
|
self.assertEqual(
|
|
value,
|
|
expected,
|
|
f"After pressing ArrowLeft, value should be {expected}"
|
|
)
|
|
|
|
# One more press should stay at 1
|
|
value = self._simulate_arrow_left(value)
|
|
self.assertEqual(value, 1, "Should stay at min value 1")
|
|
|
|
def test_keyboard_navigation_mixed_keys(self):
|
|
"""
|
|
Test mixed keyboard navigation (up, down, left, right)
|
|
"""
|
|
# Start at 3
|
|
value = 3
|
|
|
|
# Right -> 4
|
|
value = self._simulate_arrow_right(value)
|
|
self.assertEqual(value, 4)
|
|
|
|
# Left -> 3
|
|
value = self._simulate_arrow_left(value)
|
|
self.assertEqual(value, 3)
|
|
|
|
# Up -> 4
|
|
value = self._simulate_arrow_up(value)
|
|
self.assertEqual(value, 4)
|
|
|
|
# Down -> 3
|
|
value = self._simulate_arrow_down(value)
|
|
self.assertEqual(value, 3)
|
|
|
|
# Home -> 1
|
|
value = self._simulate_home_key()
|
|
self.assertEqual(value, 1)
|
|
|
|
# End -> 5
|
|
value = self._simulate_end_key()
|
|
self.assertEqual(value, 5)
|
|
|
|
def test_keyboard_navigation_consistency(self):
|
|
"""
|
|
Test that keyboard navigation is consistent across multiple calls
|
|
"""
|
|
for initial_value in range(0, self.max_stars + 1):
|
|
# Test ArrowRight consistency
|
|
result1 = self._simulate_arrow_right(initial_value)
|
|
result2 = self._simulate_arrow_right(initial_value)
|
|
result3 = self._simulate_arrow_right(initial_value)
|
|
self.assertEqual(result1, result2, "ArrowRight should be consistent")
|
|
self.assertEqual(result2, result3, "ArrowRight should be consistent")
|
|
|
|
# Test ArrowLeft consistency
|
|
if initial_value > 0:
|
|
result1 = self._simulate_arrow_left(initial_value)
|
|
result2 = self._simulate_arrow_left(initial_value)
|
|
result3 = self._simulate_arrow_left(initial_value)
|
|
self.assertEqual(result1, result2, "ArrowLeft should be consistent")
|
|
self.assertEqual(result2, result3, "ArrowLeft should be consistent")
|
|
|
|
def test_keyboard_navigation_all_values_reachable(self):
|
|
"""
|
|
Test that all rating values (1-5) are reachable via keyboard
|
|
"""
|
|
# Starting from 0, we should be able to reach all values 1-5
|
|
value = 0
|
|
reachable_values = set()
|
|
|
|
# Use ArrowRight to reach each value
|
|
for _ in range(self.max_stars):
|
|
value = self._simulate_arrow_right(value)
|
|
reachable_values.add(value)
|
|
|
|
# All values 1-5 should be reachable
|
|
expected_values = set(range(1, self.max_stars + 1))
|
|
self.assertEqual(
|
|
reachable_values,
|
|
expected_values,
|
|
f"All values {expected_values} should be reachable via keyboard"
|
|
)
|
|
|
|
def test_keyboard_navigation_independence(self):
|
|
"""
|
|
Test that keyboard navigation works independently of mouse interaction
|
|
"""
|
|
# This test verifies that keyboard navigation logic is independent
|
|
# In the actual implementation, keyboard and mouse should both work
|
|
|
|
# Simulate selecting with keyboard
|
|
keyboard_value = 0
|
|
keyboard_value = self._simulate_arrow_right(keyboard_value)
|
|
keyboard_value = self._simulate_arrow_right(keyboard_value)
|
|
keyboard_value = self._simulate_arrow_right(keyboard_value)
|
|
|
|
# Should reach 3
|
|
self.assertEqual(keyboard_value, 3, "Keyboard navigation should reach 3")
|
|
|
|
# Keyboard navigation should work from any starting point
|
|
# (simulating that mouse could have set any value)
|
|
for mouse_value in range(0, self.max_stars + 1):
|
|
# From any mouse-selected value, keyboard should work
|
|
new_value = self._simulate_arrow_right(mouse_value)
|
|
if mouse_value < self.max_stars:
|
|
self.assertEqual(
|
|
new_value,
|
|
mouse_value + 1,
|
|
f"Keyboard should work from mouse-selected value {mouse_value}"
|
|
)
|
|
|
|
def test_keyboard_navigation_rapid_input(self):
|
|
"""
|
|
Test rapid keyboard input (multiple key presses in sequence)
|
|
"""
|
|
value = 0
|
|
|
|
# Simulate rapid ArrowRight presses
|
|
for i in range(10):
|
|
value = self._simulate_arrow_right(value)
|
|
|
|
# Should cap at max value
|
|
self.assertEqual(
|
|
value,
|
|
self.max_stars,
|
|
f"Rapid ArrowRight should cap at {self.max_stars}"
|
|
)
|
|
|
|
# Simulate rapid ArrowLeft presses
|
|
for i in range(10):
|
|
value = self._simulate_arrow_left(value)
|
|
|
|
# Should cap at min value
|
|
self.assertEqual(
|
|
value,
|
|
self.min_stars,
|
|
f"Rapid ArrowLeft should cap at {self.min_stars}"
|
|
)
|
|
|
|
def test_keyboard_navigation_alternating_directions(self):
|
|
"""
|
|
Test alternating keyboard directions
|
|
"""
|
|
value = 3
|
|
|
|
# Alternate right and left
|
|
for _ in range(5):
|
|
original = value
|
|
value = self._simulate_arrow_right(value)
|
|
value = self._simulate_arrow_left(value)
|
|
# Should return to original (unless at boundary)
|
|
if original > self.min_stars and original < self.max_stars:
|
|
self.assertEqual(
|
|
value,
|
|
original,
|
|
"Alternating right/left should return to original"
|
|
)
|