Files
python-tdd/src/functional_tests/test_dash_palette.py
Disco DeDisco f9c05a3eba
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
functional_tests + CI: rename pass + structural consolidations + parallel test-FTs split — every FT file now starts with one of 6 prefixes (test_admin_* / test_bill_* / test_core_* / test_dash_* / test_game_room_* / test_trinket_*) plus the 4 page-roots test_billboard / test_dashboard / test_gameboard / test_jasmine, so the partition is unambiguous and stable for tooling (the previous mix of test_applet_*, test_room_*, test_component_*, ad-hoc names had no consistent grouping); session-side: merged test_gatekeeper_bud_btn.py into test_bud_btn.py (then user renamed to test_core_bud_btn.py) — both files drove the same #id_bud_btn UI in two contexts (post-share + gatekeeper invite) and shared the bud-btn.js skeleton, so consolidation was overdue; split test_component_cards_tarot.py into test_admin_tarot.py (just TarotAdminTest, sitting next to test_admin / test_admin_post_readonly) + 3 classes (TarotDeckTest / GameKitDeckSelectionTest / GameKitPageTest) appended to test_game_room_tray.py; updated stale test_bud_btn.py references in the test_core_bud_btn.py docstring + test_admin_post_readonly.py comment to point at the new filename; user-driven renames (22 files): test_applet_my_notes/posts → test_bill_my_*, test_applet_new_post[_line_validation] → test_bill_new_post[_line_validation], test_applet_my_sky → test_dash_my_sky, test_applet_palette → test_dash_palette, test_wallet → test_dash_wallet, test_login → test_core_login, test_navbar → test_core_navbar, test_sharing → test_core_sharing, test_layout_and_styling → test_core_styling, test_my_buds → test_bill_my_buds, test_bud_btn → test_core_bud_btn, test_deck_contribution → test_game_room_deck_contrib, test_game_invite → test_game_room_invite, test_room_gatekeeper → test_game_room_gatekeeper, test_room_role_select → test_game_room_select_role, test_room_sea_select → test_game_room_select_sea, test_room_sig_select → test_game_room_select_sig, test_room_sky_select → test_game_room_select_sky, test_room_tray → test_game_room_tray, test_component_tray_tooltip → test_game_room_tray_tooltip; the post_page.py / room_page.py helper modules from the May-12 sprint absorbed the cross-file FT imports that would otherwise have cascade-broken on these renames
.woodpecker/main.yaml — CI test-FTs step splits into parallel siblings test-FTs-non-room (22 files via `ls functional_tests/test_*.py | grep -v 'test_game_room_'`) + test-FTs-room (9 files via `ls functional_tests/test_game_room_*.py`); room cluster is the heaviest (~70% of the pre-split ~40-min wall-clock) and now runs concurrently w. the rest instead of in series; DAG explicit via depends_on on every step (Woodpecker mixes default-sequential w. depends_on awkwardly, so each step pins its prerequisite); collectstatic stays in test-two-browser-FTs only — the shared workspace propagates assets to both parallel FT steps, no race + no duplication; screendumps + build-and-push fan back in (depends_on both parallel steps); deploy-staging + deploy-prod depend on build-and-push

smoke-import: 31/31 FT modules green after the rename pass

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:06:25 -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_description(self):
self._click_swatch()
self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, "#id_tooltip_portal .tt-description"
)
)
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("Unlocked", 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")