Files
python-tdd/src/functional_tests/test_applet_palette.py
Disco DeDisco 122de3bc80
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
PALETTE: swatch preview + tooltip + OK commit — TDD
Clicking a swatch instantly swaps the body palette class for a live
preview; OK commits silently (POST, no reload); click-elsewhere or
10 s auto-dismiss reverts. Tooltip portal shows label, shoptalk,
lock state. Locked swatches show × (disabled). 20 FTs green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 02:05:27 -04:00

340 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from apps.applets.models import Applet
from apps.lyric.models import User
from .base import FunctionalTest
def _setup_palette_applet():
Applet.objects.get_or_create(
slug="palette",
defaults={"name": "Palette", "context": "dashboard"},
)
class PalettePreviewTest(FunctionalTest):
"""Clicking a swatch previews the palette on the whole body."""
def setUp(self):
super().setUp()
_setup_palette_applet()
User.objects.get_or_create(email="preview@test.io")
self.create_pre_authenticated_session("preview@test.io")
self.browser.get(self.live_server_url)
def _click_non_active_swatch(self):
swatch = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR,
".palette-item:not(:has(.swatch.active)) .swatch",
)
)
swatch.click()
return swatch
def test_clicking_swatch_adds_preview_class_to_body(self):
swatch = self._click_non_active_swatch()
palette_name = swatch.get_attribute("data-palette")
self.wait_for(
lambda: self.assertIn(
palette_name,
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
)
)
def test_body_has_only_preview_palette_during_preview(self):
# Original palette class is swapped out — only the preview class is on body
swatch = self._click_non_active_swatch()
palette_name = swatch.get_attribute("data-palette")
self.wait_for(
lambda: self.assertIn(
palette_name,
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
)
)
self.wait_for(
lambda: self.assertNotIn(
"palette-default",
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
)
)
def test_clicking_elsewhere_reverts_body_to_original_palette(self):
swatch = self._click_non_active_swatch()
palette_name = swatch.get_attribute("data-palette")
self.wait_for(
lambda: self.assertIn(
palette_name,
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
)
)
# Click outside the applet
self.browser.find_element(By.CSS_SELECTOR, "h1, h2").click()
self.wait_for(
lambda: self.assertNotIn(
palette_name,
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
)
)
# Original palette restored
self.assertIn(
"palette-default",
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
)
def test_swatch_gets_previewing_class_on_click(self):
self._click_non_active_swatch()
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, ".swatch.previewing")
)
def test_previewing_class_removed_on_dismiss(self):
self._click_non_active_swatch()
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, ".swatch.previewing")
)
self.browser.find_element(By.CSS_SELECTOR, "h1, h2").click()
self.wait_for(
lambda: self.assertEqual(
len(self.browser.find_elements(By.CSS_SELECTOR, ".swatch.previewing")),
0,
)
)
def test_auto_dismiss_after_ten_seconds(self):
swatch = self._click_non_active_swatch()
palette_name = swatch.get_attribute("data-palette")
self.wait_for(
lambda: self.assertIn(
palette_name,
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
)
)
# After 10s the preview should clear automatically
self.wait_for_slow(
lambda: self.assertNotIn(
palette_name,
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
),
timeout=15,
)
class PaletteOkButtonTest(FunctionalTest):
"""OK btn (unlocked) / × btn (locked) appear centered on clicked swatch."""
def setUp(self):
super().setUp()
_setup_palette_applet()
User.objects.get_or_create(email="okbtn@test.io")
self.create_pre_authenticated_session("okbtn@test.io")
self.browser.get(self.live_server_url)
def test_ok_btn_absent_before_click(self):
btns = self.browser.find_elements(By.CSS_SELECTOR, ".swatch .btn-confirm")
self.assertEqual(len([b for b in btns if b.is_displayed()]), 0)
def test_clicking_unlocked_swatch_shows_ok_btn_inside_swatch(self):
swatch = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR,
".palette-item:not(:has(.swatch.active)):not(:has(.swatch.locked)) .swatch",
)
)
swatch.click()
ok = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, ".swatch.previewing .btn-confirm"
)
)
self.assertTrue(ok.is_displayed())
def test_clicking_locked_swatch_shows_disabled_times_btn(self):
locked = self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, ".swatch.locked")
)
locked.click()
disabled = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, ".swatch.previewing .btn-disabled"
)
)
self.assertIn("×", disabled.text)
def test_clicking_ok_commits_palette_and_no_reload(self):
self.browser.execute_script("window._no_reload = true")
swatch = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR,
".palette-item:not(:has(.swatch.active)):not(:has(.swatch.locked)) .swatch",
)
)
palette_name = swatch.get_attribute("data-palette")
swatch.click()
ok = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, ".swatch.previewing .btn-confirm"
)
)
ok.click()
# Body ends up with only the new palette (preview cleared, committed)
self.wait_for(
lambda: self.assertNotIn(
"palette-default",
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
)
)
self.wait_for(
lambda: self.assertIn(
palette_name,
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
)
)
self.assertTrue(self.browser.execute_script("return window._no_reload === true"))
def test_btn_disappears_on_dismiss(self):
swatch = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR,
".palette-item:not(:has(.swatch.active)):not(:has(.swatch.locked)) .swatch",
)
)
swatch.click()
self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, ".swatch.previewing .btn-confirm"
)
)
self.browser.find_element(By.CSS_SELECTOR, "h1, h2").click()
self.wait_for(
lambda: self.assertEqual(
len([b for b in self.browser.find_elements(
By.CSS_SELECTOR, ".swatch .btn-confirm"
) if b.is_displayed()]),
0,
)
)
class PaletteTooltipTest(FunctionalTest):
"""Clicking a swatch shows a tooltip in #id_tooltip_portal."""
def setUp(self):
super().setUp()
_setup_palette_applet()
self.user, _ = User.objects.get_or_create(email="palettett@test.io")
self.create_pre_authenticated_session("palettett@test.io")
self.browser.get(self.live_server_url)
def _click_swatch(self, selector=".swatch"):
swatch = self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, selector)
)
swatch.click()
return swatch
def test_clicking_swatch_shows_tooltip_portal(self):
self._click_swatch()
portal = self.wait_for(
lambda: self.browser.find_element(By.ID, "id_tooltip_portal")
)
self.assertTrue(portal.is_displayed())
def test_tooltip_title_is_colored_ter_user(self):
self._click_swatch()
title = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, "#id_tooltip_portal .tt-title"
)
)
ter_user = self.browser.execute_script(
"return getComputedStyle(document.body).getPropertyValue('--terUser').trim()"
)
r, g, b = [int(x.strip()) for x in ter_user.split(",")]
color = self.browser.execute_script(
"return getComputedStyle(arguments[0]).color", title
)
self.assertIn(f"rgb({r}, {g}, {b})", color)
def test_tooltip_shows_shoptalk(self):
self._click_swatch()
self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, "#id_tooltip_portal .tt-shoptalk"
)
)
def test_unlocked_swatch_shows_lock_open_and_unlocked(self):
self._click_swatch(
".palette-item:not(:has(.swatch.locked)) .swatch"
)
lock_line = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, "#id_tooltip_portal .tt-lock"
)
)
self.assertIn("Unlocked", lock_line.text)
self.assertTrue(lock_line.find_elements(By.CSS_SELECTOR, ".fa-lock-open"))
def test_locked_swatch_shows_lock_and_locked(self):
self._click_swatch(".swatch.locked")
lock_line = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, "#id_tooltip_portal .tt-lock"
)
)
self.assertIn("Locked", lock_line.text)
self.assertTrue(lock_line.find_elements(By.CSS_SELECTOR, ".fa-lock"))
def test_unlocked_tooltip_shows_default_label(self):
self._click_swatch(".palette-item:not(:has(.swatch.locked)) .swatch")
lock_line = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, "#id_tooltip_portal .tt-lock"
)
)
self.assertIn("Default", lock_line.text)
def test_tooltip_dismisses_on_click_outside(self):
self._click_swatch()
self.wait_for(
lambda: self.assertTrue(
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
)
)
self.browser.find_element(By.CSS_SELECTOR, "h1, h2").click()
self.wait_for(
lambda: self.assertFalse(
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
)
)
class SiteThemeTest(FunctionalTest):
def test_page_renders_with_earthman_palette(self):
self.browser.get(self.live_server_url)
body = self.browser.find_element(By.TAG_NAME, "body")
self.assertIn("palette-default", body.get_attribute("class"))
class LightPaletteTest(FunctionalTest):
def test_light_palette_tooltip_uses_white_background(self):
user, _ = User.objects.get_or_create(email="light@example.com")
user.palette = "palette-oblivion-light"
user.save()
self.create_pre_authenticated_session("light@example.com")
self.browser.get(self.live_server_url + "/dashboard/wallet/")
body = self.browser.find_element(By.TAG_NAME, "body")
tooltip_bg = self.browser.execute_script(
"return getComputedStyle(arguments[0]).getPropertyValue('--tooltip-bg').trim()",
body,
)
self.assertEqual(tooltip_bg, "255, 255, 255")