helpdesk_rating_five_stars/tests/test_keyboard_navigation.py
2025-11-26 10:39:26 +07:00

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"
)