-
Notifications
You must be signed in to change notification settings - Fork 0
Sprint 46: Polish coaching tree visuals #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| import { renderToStaticMarkup } from 'react-dom/server'; | ||
| import { describe, expect, it, vi } from 'vitest'; | ||
| import { CoachingTree } from './CoachingTree'; | ||
|
|
||
| const rootCoach = { | ||
| id: 'root-hc', | ||
| name: 'Dana Vale', | ||
| role: 'HC', | ||
| archetype: 'strategist', | ||
| traits: ['calm_clock'], | ||
| mentorCoachId: 'mentor-direct', | ||
| disciples: ['disciple-one'], | ||
| yearsUnderMentor: 4, | ||
| }; | ||
|
|
||
| const directMentor = { | ||
| id: 'mentor-direct', | ||
| name: 'Mika Stone', | ||
| role: 'HC', | ||
| archetype: 'defensive_minded', | ||
| traits: ['red_zone_wall'], | ||
| mentorCoachId: 'mentor-indirect', | ||
| disciples: ['root-hc'], | ||
| yearsUnderMentor: 5, | ||
| }; | ||
|
|
||
| const indirectMentor = { | ||
| id: 'mentor-indirect', | ||
| name: 'Alex North', | ||
| role: 'HC', | ||
| archetype: 'disciplinarian', | ||
| traits: ['accountability'], | ||
| mentorCoachId: null, | ||
| disciples: ['mentor-direct'], | ||
| yearsUnderMentor: 0, | ||
| }; | ||
|
|
||
| const disciple = { | ||
| id: 'disciple-one', | ||
| name: 'River Cross', | ||
| role: 'OC', | ||
| archetype: 'offensive_minded', | ||
| traits: ['motion_packages'], | ||
| mentorCoachId: 'root-hc', | ||
| disciples: [], | ||
| yearsUnderMentor: 2, | ||
| }; | ||
|
|
||
| const userTeam = { | ||
| id: 'user', | ||
| abbr: 'USR', | ||
| staff: { hc: rootCoach, oc: null, dc: null }, | ||
| }; | ||
|
|
||
| const mockState = { | ||
| game: { | ||
| year: 2032, | ||
| eventLog: [], | ||
| coachingHistory: [], | ||
| teams: { | ||
| user: userTeam, | ||
| north: { id: 'north', abbr: 'NTH', staff: { hc: directMentor, oc: null, dc: null } }, | ||
| summit: { id: 'summit', abbr: 'SUM', staff: { hc: indirectMentor, oc: null, dc: null } }, | ||
| metro: { id: 'metro', abbr: 'MET', staff: { hc: null, oc: disciple, dc: null } }, | ||
| }, | ||
| }, | ||
| userTeam, | ||
| }; | ||
|
|
||
| vi.mock('@mfd/engine', async (importOriginal) => { | ||
| const actual = await importOriginal<typeof import('@mfd/engine')>(); | ||
| return { | ||
| ...actual, | ||
| buildCoachingLegacy: () => ({ | ||
| treeDepth: 3, | ||
| headCoachesProduced: 1, | ||
| coordinatorsPlaced: 1, | ||
| retiredWithEpilogue: 0, | ||
| notableProteges: [disciple], | ||
| }), | ||
| }; | ||
| }); | ||
|
|
||
| vi.mock('../../app/store/game-store', () => ({ | ||
| useGameStore: (selector: (state: typeof mockState) => unknown) => selector(mockState), | ||
| selectUserTeam: (state: typeof mockState) => state.userTeam, | ||
| })); | ||
|
|
||
| describe('CoachingTree', () => { | ||
| it('renders archetype glyphs next to each coach card', () => { | ||
| const markup = renderToStaticMarkup(<CoachingTree />); | ||
|
|
||
| expect(markup.match(/data-coach-archetype-glyph=/g)).toHaveLength(4); | ||
| }); | ||
|
|
||
| it('renders SVG connection lines between linked coaches', () => { | ||
| const markup = renderToStaticMarkup(<CoachingTree />); | ||
|
|
||
| expect(markup).toContain('data-coaching-tree-connection-lines="true"'); | ||
| expect(markup).toContain('data-coaching-tree-line="true"'); | ||
| }); | ||
|
|
||
| it('uses gold stroke for direct mentor relationships', () => { | ||
| const markup = renderToStaticMarkup(<CoachingTree />); | ||
|
|
||
| expect(markup).toContain('data-line-kind="direct-mentor"'); | ||
| expect(markup).toContain('stroke="var(--mfd-gold)"'); | ||
| }); | ||
|
|
||
| it('uses dashed connection lines for indirect relationships', () => { | ||
| const markup = renderToStaticMarkup(<CoachingTree />); | ||
|
|
||
| expect(markup).toContain('data-line-kind="indirect-mentor"'); | ||
| expect(markup).toContain('stroke-dasharray="5 5"'); | ||
| }); | ||
|
|
||
| it('preserves the existing coaching-tree behavior surface', () => { | ||
| const markup = renderToStaticMarkup(<CoachingTree />); | ||
|
|
||
| expect(markup).toContain('COACHING TREE'); | ||
| expect(markup).toContain('TREE DEPTH'); | ||
| expect(markup).toContain('DANA VALE'); | ||
| expect(markup).toContain('MIKA STONE'); | ||
| expect(markup).toContain('RIVER CROSS'); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
apps/web/src/features/coaching/coachArchetypeGlyphSvg.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { renderToStaticMarkup } from 'react-dom/server'; | ||
| import { describe, expect, it } from 'vitest'; | ||
| import { COACH_ARCHETYPE_GLYPHS, CoachArchetypeGlyphSvg } from './coachArchetypeGlyphSvg'; | ||
|
|
||
| describe('CoachArchetypeGlyphSvg', () => { | ||
| it('renders the offensive_mind variant with route path data', () => { | ||
| const markup = renderToStaticMarkup(<CoachArchetypeGlyphSvg archetype="offensive_mind" label="Offense" />); | ||
|
|
||
| expect(markup).toContain('data-coach-archetype-glyph="offensive_mind"'); | ||
| expect(markup).toContain('data-glyph-routes="true"'); | ||
| }); | ||
|
|
||
| it('renders all 6 glyph variants without error', () => { | ||
| for (const archetype of COACH_ARCHETYPE_GLYPHS) { | ||
| const markup = renderToStaticMarkup(<CoachArchetypeGlyphSvg archetype={archetype} label={archetype} />); | ||
|
|
||
| expect(markup).toContain(`data-coach-archetype-glyph="${archetype}"`); | ||
| } | ||
| }); | ||
|
|
||
| it('renders a shield for defensive_mind', () => { | ||
| const markup = renderToStaticMarkup(<CoachArchetypeGlyphSvg archetype="defensive_mind" label="Defense" />); | ||
|
|
||
| expect(markup).toContain('data-glyph-shield="true"'); | ||
| }); | ||
|
|
||
| it('renders a handshake for players_coach', () => { | ||
| const markup = renderToStaticMarkup(<CoachArchetypeGlyphSvg archetype="players_coach" label="Players" />); | ||
|
|
||
| expect(markup).toContain('data-glyph-handshake="true"'); | ||
| }); | ||
|
|
||
| it('renders a flame for fire_starter', () => { | ||
| const markup = renderToStaticMarkup(<CoachArchetypeGlyphSvg archetype="fire_starter" label="Fire" />); | ||
|
|
||
| expect(markup).toContain('data-glyph-flame="true"'); | ||
| }); | ||
|
|
||
| it('renders distinct SVG path data across every glyph', () => { | ||
| const signatures = COACH_ARCHETYPE_GLYPHS.map((archetype) => | ||
| renderToStaticMarkup(<CoachArchetypeGlyphSvg archetype={archetype} label={archetype} />).match(/d="[^"]+"/g)?.join('|') ?? '', | ||
| ); | ||
|
|
||
| expect(new Set(signatures)).toHaveLength(COACH_ARCHETYPE_GLYPHS.length); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The disciple path formula pushes Y coordinates beyond the
viewBoxheight once there are many disciples (31 + idx * 10exceeds 100 atidx >= 7), so later disciples lose their connector lines even though cards still render. This affects long-running saves where a head coach has 8+ disciples and makes the new relationship overlay incomplete for valid data.Useful? React with 👍 / 👎.