golfgame/tests/e2e/utils/selectors.ts
adlee-was-taken 9fc6b83bba v3.0.0: V3 features, server refactoring, and documentation overhaul
- Extract WebSocket handlers from main.py into handlers.py
- Add V3 feature docs (dealer rotation, dealing animation, round end reveal,
  column pair celebration, final turn urgency, opponent thinking, score tallying,
  card hover/selection, knock early drama, column pair indicator, swap animation
  improvements, draw source distinction, card value tooltips, active rules context,
  discard pile history, realistic card sounds)
- Add V3 refactoring docs (ai.py, main.py/game.py, misc improvements)
- Add installation guide with Docker, systemd, and nginx setup
- Add helper scripts (install.sh, dev-server.sh, docker-build.sh)
- Add animation flow diagrams documentation
- Add test files for handlers, rooms, and V3 features
- Add e2e test specs for V3 features
- Update README with complete project structure and current tech stack
- Update CLAUDE.md with full architecture tree and server layer descriptions
- Update .env.example to reflect PostgreSQL (remove SQLite references)
- Update .gitignore to exclude virtualenv files, .claude/, and .db files
- Remove tracked virtualenv files (bin/, lib64, pyvenv.cfg)
- Remove obsolete game_log.py (SQLite) and games.db

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 10:03:45 -05:00

182 lines
4.9 KiB
TypeScript

/**
* DOM selector constants for the Golf game
* Extracted from client/index.html and client/app.js
*/
export const SELECTORS = {
// Screens
screens: {
lobby: '#lobby-screen',
waiting: '#waiting-screen',
game: '#game-screen',
rules: '#rules-screen',
},
// Lobby elements
lobby: {
playerNameInput: '#player-name',
roomCodeInput: '#room-code',
createRoomBtn: '#create-room-btn',
joinRoomBtn: '#join-room-btn',
error: '#lobby-error',
},
// Waiting room elements
waiting: {
roomCode: '#display-room-code',
copyCodeBtn: '#copy-room-code',
shareBtn: '#share-room-link',
playersList: '#players-list',
hostSettings: '#host-settings',
startGameBtn: '#start-game-btn',
leaveRoomBtn: '#leave-room-btn',
addCpuBtn: '#add-cpu-btn',
removeCpuBtn: '#remove-cpu-btn',
cpuModal: '#cpu-select-modal',
cpuProfilesGrid: '#cpu-profiles-grid',
cancelCpuBtn: '#cancel-cpu-btn',
addSelectedCpusBtn: '#add-selected-cpus-btn',
// Settings
numDecks: '#num-decks',
numRounds: '#num-rounds',
initialFlips: '#initial-flips',
flipMode: '#flip-mode',
knockPenalty: '#knock-penalty',
},
// Game screen elements
game: {
// Header
currentRound: '#current-round',
totalRounds: '#total-rounds',
statusMessage: '#status-message',
finalTurnBadge: '#final-turn-badge',
muteBtn: '#mute-btn',
leaveGameBtn: '#leave-game-btn',
activeRulesBar: '#active-rules-bar',
// Table
opponentsRow: '#opponents-row',
playerArea: '.player-area',
playerCards: '#player-cards',
playerHeader: '#player-header',
yourScore: '#your-score',
// Deck and discard
deckArea: '.deck-area',
deck: '#deck',
discard: '#discard',
discardContent: '#discard-content',
discardBtn: '#discard-btn',
skipFlipBtn: '#skip-flip-btn',
knockEarlyBtn: '#knock-early-btn',
// Held card
heldCardSlot: '#held-card-slot',
heldCardDisplay: '#held-card-display',
heldCardFloating: '#held-card-floating',
heldCardFloatingContent: '#held-card-floating-content',
// Scoreboard
scoreboard: '#scoreboard',
scoreTable: '#score-table tbody',
standingsList: '#standings-list',
nextRoundBtn: '#next-round-btn',
newGameBtn: '#new-game-btn',
gameButtons: '#game-buttons',
// Card layer for animations
cardLayer: '#card-layer',
},
// Card-related selectors
cards: {
// Player's own cards (0-5)
playerCard: (index: number) => `#player-cards .card:nth-child(${index + 1})`,
playerCardSlot: (index: number) => `#player-cards .card-slot:nth-child(${index + 1})`,
// Opponent cards
opponentArea: (index: number) => `.opponent-area:nth-child(${index + 1})`,
opponentCard: (oppIndex: number, cardIndex: number) =>
`.opponent-area:nth-child(${oppIndex + 1}) .card-grid .card:nth-child(${cardIndex + 1})`,
// Card states
faceUp: '.card-front',
faceDown: '.card-back',
clickable: '.clickable',
selected: '.selected',
},
// CSS classes for state detection
classes: {
active: 'active',
hidden: 'hidden',
clickable: 'clickable',
selected: 'selected',
faceUp: 'card-front',
faceDown: 'card-back',
red: 'red',
black: 'black',
joker: 'joker',
currentTurn: 'current-turn',
roundWinner: 'round-winner',
yourTurnToDraw: 'your-turn-to-draw',
hasCard: 'has-card',
pickedUp: 'picked-up',
disabled: 'disabled',
// V3 classes
canSwap: 'can-swap',
paired: 'paired',
pairTop: 'pair-top',
pairBottom: 'pair-bottom',
ruleHighlighted: 'rule-highlighted',
thinkingHidden: 'hidden',
},
// Animation-related
animations: {
swapAnimation: '#swap-animation',
swapCardFromHand: '#swap-card-from-hand',
animCard: '.anim-card',
realCard: '.real-card',
},
// V3 feature selectors
v3: {
thinkingIndicator: '.thinking-indicator',
cardTooltip: '.card-value-tooltip',
tooltipValue: '.tooltip-value',
tooltipNote: '.tooltip-note',
ruleTag: '.rule-tag',
ruleHighlighted: '.rule-tag.rule-highlighted',
ruleMessage: '.rule-message',
pairIndicator: '.paired',
dealerBadge: '.dealer-badge',
knockBanner: '#knock-banner',
knockConfirmDialog: '#knock-confirm-dialog',
cardValueOverlay: '.card-value-overlay',
pairCancelOverlay: '.pair-cancel-overlay',
},
};
/**
* Build a selector for a card in the player's grid
*/
export function playerCardSelector(position: number): string {
return SELECTORS.cards.playerCard(position);
}
/**
* Build a selector for a clickable card
*/
export function clickableCardSelector(position: number): string {
return `${SELECTORS.cards.playerCard(position)}.${SELECTORS.classes.clickable}`;
}
/**
* Build a selector for an opponent's card
*/
export function opponentCardSelector(opponentIndex: number, cardPosition: number): string {
return SELECTORS.cards.opponentCard(opponentIndex, cardPosition);
}