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

559 lines
19 KiB
Python

# -*- coding: utf-8 -*-
from odoo.tests.common import TransactionCase
from hypothesis import given, strategies as st, settings
class TestAriaLabels(TransactionCase):
"""
Test cases for ARIA label accessibility
Property 21: ARIA labels present for accessibility
For any star in the rating form, it should have an appropriate
ARIA label for screen reader compatibility.
Validates: Requirements 8.3
"""
def setUp(self):
super(TestAriaLabels, self).setUp()
# The star rating widget has 5 stars
self.max_stars = 5
self.min_stars = 1
def _get_aria_label_for_star(self, star_number):
"""
Get the ARIA label for a specific star.
This mirrors the logic in rating_stars.js getAriaLabel():
- For star 1: "Rate 1 star out of 5"
- For stars 2-5: "Rate N stars out of 5"
Args:
star_number: The star number (1-5)
Returns:
The ARIA label string for that star
"""
if star_number == 1:
return f"Rate 1 star out of {self.max_stars}"
return f"Rate {star_number} stars out of {self.max_stars}"
def _get_container_aria_label(self, selected_value):
"""
Get the ARIA label for the star container.
This mirrors the logic in rating_stars.xml template:
- Container has role="slider"
- aria-label: "Rating: N out of 5 stars"
Args:
selected_value: The currently selected rating value (0-5)
Returns:
The ARIA label string for the container
"""
return f"Rating: {selected_value} out of {self.max_stars} stars"
def _verify_aria_label_property(self, star_number):
"""
Verify that a star has an appropriate ARIA label.
The property states: For any star in the rating form, it should have
an appropriate ARIA label for screen reader compatibility.
Args:
star_number: The star number to verify (1-5)
"""
# Get the ARIA label for this star
aria_label = self._get_aria_label_for_star(star_number)
# Property 1: ARIA label should exist (not None or empty)
self.assertIsNotNone(
aria_label,
f"Star {star_number} should have an ARIA label"
)
self.assertTrue(
len(aria_label) > 0,
f"Star {star_number} ARIA label should not be empty"
)
# Property 2: ARIA label should contain the star number
self.assertIn(
str(star_number),
aria_label,
f"ARIA label should contain star number {star_number}"
)
# Property 3: ARIA label should contain "Rate" to indicate action
self.assertIn(
"Rate",
aria_label,
f"ARIA label should contain 'Rate' to indicate action"
)
# Property 4: ARIA label should contain "out of" to indicate scale
self.assertIn(
"out of",
aria_label,
f"ARIA label should contain 'out of' to indicate scale"
)
# Property 5: ARIA label should contain max stars
self.assertIn(
str(self.max_stars),
aria_label,
f"ARIA label should contain max stars {self.max_stars}"
)
# Property 6: ARIA label should use correct singular/plural form
if star_number == 1:
# Should say "1 star" (singular)
self.assertIn(
"1 star",
aria_label,
f"ARIA label for star 1 should use singular 'star'"
)
self.assertNotIn(
"1 stars",
aria_label,
f"ARIA label for star 1 should not use plural 'stars'"
)
else:
# Should say "N stars" (plural)
self.assertIn(
f"{star_number} stars",
aria_label,
f"ARIA label for star {star_number} should use plural 'stars'"
)
return aria_label
def _verify_container_aria_attributes(self, selected_value):
"""
Verify that the container has appropriate ARIA attributes.
The container should have:
- role="slider"
- aria-label describing current rating
- aria-valuemin="1"
- aria-valuemax="5"
- aria-valuenow=selected_value
- aria-readonly (when readonly)
Args:
selected_value: The currently selected rating value (0-5)
"""
# Get container ARIA label
container_label = self._get_container_aria_label(selected_value)
# Property 1: Container should have ARIA label
self.assertIsNotNone(
container_label,
"Container should have an ARIA label"
)
self.assertTrue(
len(container_label) > 0,
"Container ARIA label should not be empty"
)
# Property 2: Container label should contain "Rating"
self.assertIn(
"Rating",
container_label,
"Container ARIA label should contain 'Rating'"
)
# Property 3: Container label should contain selected value
self.assertIn(
str(selected_value),
container_label,
f"Container ARIA label should contain selected value {selected_value}"
)
# Property 4: Container label should contain max stars
self.assertIn(
str(self.max_stars),
container_label,
f"Container ARIA label should contain max stars {self.max_stars}"
)
# Property 5: Container label should contain "out of"
self.assertIn(
"out of",
container_label,
"Container ARIA label should contain 'out of'"
)
return container_label
# Feature: helpdesk-rating-five-stars, Property 21: ARIA labels present for accessibility
@given(star_number=st.integers(min_value=1, max_value=5))
@settings(max_examples=100, deadline=None)
def test_property_aria_labels_present(self, star_number):
"""
Property 21: ARIA labels present for accessibility
For any star in the rating form (1-5), the star should have an
appropriate ARIA label for screen reader compatibility.
This tests that:
1. Each star has a non-empty ARIA label
2. ARIA label contains the star number
3. ARIA label indicates the action ("Rate")
4. ARIA label indicates the scale ("out of 5")
5. ARIA label uses correct singular/plural form
Validates: Requirements 8.3
"""
self._verify_aria_label_property(star_number)
# Feature: helpdesk-rating-five-stars, Property 21: ARIA labels present for accessibility
@given(selected_value=st.integers(min_value=0, max_value=5))
@settings(max_examples=100, deadline=None)
def test_property_container_aria_attributes(self, selected_value):
"""
Property 21: ARIA labels present for accessibility (Container)
For any selected rating value (0-5), the container should have
appropriate ARIA attributes for screen reader compatibility.
This tests that:
1. Container has an ARIA label describing current rating
2. ARIA label contains "Rating"
3. ARIA label contains the selected value
4. ARIA label indicates the scale
Validates: Requirements 8.3
"""
self._verify_container_aria_attributes(selected_value)
def test_aria_label_for_each_star(self):
"""
Test that each star (1-5) has a proper ARIA label
"""
for star_number in range(1, self.max_stars + 1):
aria_label = self._get_aria_label_for_star(star_number)
# Verify label exists
self.assertIsNotNone(aria_label)
self.assertTrue(len(aria_label) > 0)
# Verify label contains key information
self.assertIn(str(star_number), aria_label)
self.assertIn("Rate", aria_label)
self.assertIn("out of", aria_label)
self.assertIn(str(self.max_stars), aria_label)
def test_aria_label_singular_plural(self):
"""
Test that ARIA labels use correct singular/plural form
"""
# Star 1 should use singular "star"
label_1 = self._get_aria_label_for_star(1)
self.assertIn("1 star", label_1)
self.assertNotIn("1 stars", label_1)
# Stars 2-5 should use plural "stars"
for star_number in range(2, self.max_stars + 1):
label = self._get_aria_label_for_star(star_number)
self.assertIn(f"{star_number} stars", label)
self.assertNotIn(f"{star_number} star out", label)
def test_aria_label_format(self):
"""
Test the exact format of ARIA labels
"""
# Test star 1
label_1 = self._get_aria_label_for_star(1)
self.assertEqual(
label_1,
"Rate 1 star out of 5",
"Star 1 ARIA label should match expected format"
)
# Test star 2
label_2 = self._get_aria_label_for_star(2)
self.assertEqual(
label_2,
"Rate 2 stars out of 5",
"Star 2 ARIA label should match expected format"
)
# Test star 3
label_3 = self._get_aria_label_for_star(3)
self.assertEqual(
label_3,
"Rate 3 stars out of 5",
"Star 3 ARIA label should match expected format"
)
# Test star 4
label_4 = self._get_aria_label_for_star(4)
self.assertEqual(
label_4,
"Rate 4 stars out of 5",
"Star 4 ARIA label should match expected format"
)
# Test star 5
label_5 = self._get_aria_label_for_star(5)
self.assertEqual(
label_5,
"Rate 5 stars out of 5",
"Star 5 ARIA label should match expected format"
)
def test_container_aria_label_format(self):
"""
Test the exact format of container ARIA label
"""
# Test with different selected values
for value in range(0, self.max_stars + 1):
container_label = self._get_container_aria_label(value)
expected = f"Rating: {value} out of 5 stars"
self.assertEqual(
container_label,
expected,
f"Container ARIA label for value {value} should match expected format"
)
def test_aria_labels_are_unique(self):
"""
Test that each star has a unique ARIA label
"""
labels = []
for star_number in range(1, self.max_stars + 1):
label = self._get_aria_label_for_star(star_number)
labels.append(label)
# All labels should be unique
unique_labels = set(labels)
self.assertEqual(
len(unique_labels),
len(labels),
"Each star should have a unique ARIA label"
)
def test_aria_labels_are_descriptive(self):
"""
Test that ARIA labels are descriptive enough for screen readers
"""
for star_number in range(1, self.max_stars + 1):
label = self._get_aria_label_for_star(star_number)
# Label should be at least 10 characters (descriptive enough)
self.assertGreaterEqual(
len(label),
10,
f"ARIA label for star {star_number} should be descriptive (at least 10 chars)"
)
# Label should contain spaces (not just concatenated words)
self.assertIn(
" ",
label,
f"ARIA label for star {star_number} should contain spaces"
)
def test_aria_labels_consistency(self):
"""
Test that ARIA labels are consistent across multiple calls
"""
for star_number in range(1, self.max_stars + 1):
# Get label multiple times
label1 = self._get_aria_label_for_star(star_number)
label2 = self._get_aria_label_for_star(star_number)
label3 = self._get_aria_label_for_star(star_number)
# All should be identical
self.assertEqual(
label1,
label2,
f"ARIA label for star {star_number} should be consistent"
)
self.assertEqual(
label2,
label3,
f"ARIA label for star {star_number} should be consistent"
)
def test_aria_labels_no_special_characters(self):
"""
Test that ARIA labels don't contain problematic special characters
"""
for star_number in range(1, self.max_stars + 1):
label = self._get_aria_label_for_star(star_number)
# Should not contain HTML tags
self.assertNotIn("<", label)
self.assertNotIn(">", label)
# Should not contain quotes that could break attributes
self.assertNotIn('"', label)
# Should not contain newlines
self.assertNotIn("\n", label)
self.assertNotIn("\r", label)
def test_aria_labels_screen_reader_friendly(self):
"""
Test that ARIA labels are screen reader friendly
"""
for star_number in range(1, self.max_stars + 1):
label = self._get_aria_label_for_star(star_number)
# Should start with an action verb for clarity
self.assertTrue(
label.startswith("Rate"),
f"ARIA label should start with action verb 'Rate'"
)
# Should be in sentence case (not all caps)
self.assertNotEqual(
label,
label.upper(),
"ARIA label should not be all uppercase"
)
def test_container_aria_attributes_for_all_values(self):
"""
Test container ARIA attributes for all possible rating values
"""
for value in range(0, self.max_stars + 1):
container_label = self._get_container_aria_label(value)
# Verify label exists and is descriptive
self.assertIsNotNone(container_label)
self.assertTrue(len(container_label) > 0)
# Verify label contains key information
self.assertIn("Rating", container_label)
self.assertIn(str(value), container_label)
self.assertIn("out of", container_label)
self.assertIn(str(self.max_stars), container_label)
def test_aria_labels_internationalization_ready(self):
"""
Test that ARIA labels are structured for easy internationalization
"""
# The current implementation uses English strings
# This test verifies the structure is consistent and could be translated
for star_number in range(1, self.max_stars + 1):
label = self._get_aria_label_for_star(star_number)
# Label should follow a consistent pattern
# "Rate X star(s) out of Y"
parts = label.split()
# Should have at least 5 words
self.assertGreaterEqual(
len(parts),
5,
f"ARIA label should have consistent structure with multiple words"
)
# First word should be "Rate"
self.assertEqual(
parts[0],
"Rate",
"ARIA label should start with 'Rate'"
)
def test_aria_labels_wcag_compliance(self):
"""
Test that ARIA labels meet WCAG 2.1 AA accessibility standards
"""
# WCAG requires that interactive elements have accessible names
# and that the names are descriptive
for star_number in range(1, self.max_stars + 1):
label = self._get_aria_label_for_star(star_number)
# 1. Label must exist (WCAG 4.1.2)
self.assertIsNotNone(label)
self.assertTrue(len(label) > 0)
# 2. Label must be descriptive (WCAG 2.4.6)
# Should describe both the action and the result
self.assertIn("Rate", label) # Action
self.assertIn(str(star_number), label) # Result
# 3. Label must provide context (WCAG 3.3.2)
# Should indicate the scale
self.assertIn("out of", label)
self.assertIn(str(self.max_stars), label)
def test_aria_labels_all_stars_have_labels(self):
"""
Test that all 5 stars have ARIA labels (no missing labels)
"""
labels = []
for star_number in range(1, self.max_stars + 1):
label = self._get_aria_label_for_star(star_number)
labels.append(label)
# Should have exactly 5 labels
self.assertEqual(
len(labels),
self.max_stars,
f"Should have {self.max_stars} ARIA labels"
)
# All labels should be non-empty
for i, label in enumerate(labels, start=1):
self.assertTrue(
len(label) > 0,
f"Star {i} should have a non-empty ARIA label"
)
def test_aria_labels_boundary_values(self):
"""
Test ARIA labels for boundary values (first and last star)
"""
# First star (1)
label_first = self._get_aria_label_for_star(1)
self.assertEqual(label_first, "Rate 1 star out of 5")
# Last star (5)
label_last = self._get_aria_label_for_star(self.max_stars)
self.assertEqual(label_last, f"Rate {self.max_stars} stars out of 5")
def test_container_aria_label_boundary_values(self):
"""
Test container ARIA label for boundary values
"""
# No rating (0)
label_zero = self._get_container_aria_label(0)
self.assertEqual(label_zero, "Rating: 0 out of 5 stars")
# Maximum rating (5)
label_max = self._get_container_aria_label(self.max_stars)
self.assertEqual(label_max, f"Rating: {self.max_stars} out of 5 stars")
def test_aria_labels_provide_complete_information(self):
"""
Test that ARIA labels provide complete information for screen reader users
"""
for star_number in range(1, self.max_stars + 1):
label = self._get_aria_label_for_star(star_number)
# A screen reader user should understand:
# 1. What action they can take ("Rate")
self.assertIn("Rate", label)
# 2. What value they're selecting (the star number)
self.assertIn(str(star_number), label)
# 3. What the scale is ("out of 5")
self.assertIn("out of", label)
self.assertIn(str(self.max_stars), label)
# 4. The unit of measurement ("star" or "stars")
self.assertTrue(
"star" in label or "stars" in label,
"ARIA label should contain 'star' or 'stars'"
)