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