@@ -18,18 +18,19 @@ test.describe('Timeline Demo', () => {
1818 const timeline = page . locator ( '[data-testid="timeline-container"]' ) . first ( ) ;
1919 await expect ( timeline ) . toBeVisible ( ) ;
2020
21- // Check that events are loaded (Sprint 0 scaffold shows event count)
22- await expect ( timeline ) . toContainText ( 'events loaded' ) ;
21+ // Sprint 1: Check that timeline bands are rendered
22+ await expect ( timeline . locator ( '.timeline-band--detail' ) ) . toBeVisible ( ) ;
23+ await expect ( timeline . locator ( '.timeline-band--overview' ) ) . toBeVisible ( ) ;
2324 } ) ;
2425
2526 test ( 'timeline component renders with URL data source' , async ( { page } ) => {
2627 // Check that the second timeline (URL data) loads
2728 const timelines = page . locator ( '[data-testid="timeline-container"]' ) ;
2829 await expect ( timelines ) . toHaveCount ( 2 ) ;
2930
30- // Second timeline should also show events
31+ // Second timeline should also show bands
3132 const secondTimeline = timelines . nth ( 1 ) ;
32- await expect ( secondTimeline ) . toContainText ( 'events loaded' ) ;
33+ await expect ( secondTimeline . locator ( '.timeline-band--detail' ) ) . toBeVisible ( ) ;
3334 } ) ;
3435
3536 test ( 'page has no console errors' , async ( { page } ) => {
@@ -52,6 +53,163 @@ test.describe('Timeline Demo', () => {
5253 } ) ;
5354} ) ;
5455
56+ test . describe ( 'Sprint 1: Timeline MVP Features' , ( ) => {
57+ test . beforeEach ( async ( { page } ) => {
58+ await page . goto ( '/' ) ;
59+ } ) ;
60+
61+ test ( 'renders two bands (detail + overview)' , async ( { page } ) => {
62+ const timeline = page . locator ( '[data-testid="timeline-container"]' ) . first ( ) ;
63+
64+ // Check both band types are present
65+ const detailBand = timeline . locator ( '.timeline-band--detail' ) ;
66+ const overviewBand = timeline . locator ( '.timeline-band--overview' ) ;
67+
68+ await expect ( detailBand ) . toBeVisible ( ) ;
69+ await expect ( overviewBand ) . toBeVisible ( ) ;
70+
71+ // Detail band should be larger than overview
72+ const detailBox = await detailBand . boundingBox ( ) ;
73+ const overviewBox = await overviewBand . boundingBox ( ) ;
74+ expect ( detailBox ! . height ) . toBeGreaterThan ( overviewBox ! . height ) ;
75+ } ) ;
76+
77+ test ( 'time scale shows labels' , async ( { page } ) => {
78+ const timeline = page . locator ( '[data-testid="timeline-container"]' ) . first ( ) ;
79+
80+ // Check that time scale labels are rendered
81+ const scaleLabels = timeline . locator ( '.timeline-scale__label' ) ;
82+ await expect ( scaleLabels . first ( ) ) . toBeVisible ( ) ;
83+ } ) ;
84+
85+ test ( 'pan interaction works' , async ( { page } ) => {
86+ const timeline = page . locator ( '[data-testid="timeline-container"]' ) . first ( ) ;
87+ const band = timeline . locator ( '.timeline-band--detail' ) ;
88+
89+ // Verify band has grab cursor (indicates pan is enabled)
90+ await expect ( band ) . toHaveCSS ( 'cursor' , 'grab' ) ;
91+
92+ // Test pan via keyboard (more reliable in Playwright than mouse drag)
93+ await band . click ( ) ; // Focus on band
94+
95+ // Get initial scale label text
96+ const initialLabel = await timeline . locator ( '.timeline-scale__label' ) . first ( ) . textContent ( ) ;
97+
98+ // Pan using keyboard (multiple times for visible change)
99+ for ( let i = 0 ; i < 10 ; i ++ ) {
100+ await page . keyboard . press ( 'ArrowRight' ) ;
101+ }
102+
103+ await page . waitForTimeout ( 100 ) ;
104+
105+ // Label should have changed after panning
106+ const newLabel = await timeline . locator ( '.timeline-scale__label' ) . first ( ) . textContent ( ) ;
107+ expect ( newLabel ) . not . toBe ( initialLabel ) ;
108+ } ) ;
109+
110+ test ( 'event markers are visible' , async ( { page } ) => {
111+ const timeline = page . locator ( '[data-testid="timeline-container"]' ) . first ( ) ;
112+
113+ // Wait for events to render
114+ await page . waitForTimeout ( 500 ) ;
115+
116+ // Check that event dots are rendered
117+ const eventDots = timeline . locator ( '.timeline-event__dot' ) ;
118+ const count = await eventDots . count ( ) ;
119+ expect ( count ) . toBeGreaterThan ( 0 ) ;
120+ } ) ;
121+
122+ test ( 'event click shows popup' , async ( { page } ) => {
123+ const timeline = page . locator ( '[data-testid="timeline-container"]' ) . first ( ) ;
124+
125+ // Wait for events to render
126+ await page . waitForTimeout ( 500 ) ;
127+
128+ // Find and click an event
129+ const event = timeline . locator ( '.timeline-event' ) . first ( ) ;
130+ await event . click ( ) ;
131+
132+ // Popup should appear
133+ const popup = page . locator ( '.timeline-popup' ) ;
134+ await expect ( popup ) . toBeVisible ( ) ;
135+
136+ // Popup should have title and close button
137+ await expect ( popup . locator ( '.timeline-popup__title' ) ) . toBeVisible ( ) ;
138+ await expect ( popup . locator ( '.timeline-popup__close' ) ) . toBeVisible ( ) ;
139+ } ) ;
140+
141+ test ( 'popup closes on close button click' , async ( { page } ) => {
142+ const timeline = page . locator ( '[data-testid="timeline-container"]' ) . first ( ) ;
143+
144+ // Wait for events to render
145+ await page . waitForTimeout ( 500 ) ;
146+
147+ // Open popup
148+ const event = timeline . locator ( '.timeline-event' ) . first ( ) ;
149+ await event . click ( ) ;
150+
151+ const popup = page . locator ( '.timeline-popup' ) ;
152+ await expect ( popup ) . toBeVisible ( ) ;
153+
154+ // Close popup
155+ await popup . locator ( '.timeline-popup__close' ) . click ( ) ;
156+ await expect ( popup ) . not . toBeVisible ( ) ;
157+ } ) ;
158+
159+ test ( 'popup closes on Escape key' , async ( { page } ) => {
160+ const timeline = page . locator ( '[data-testid="timeline-container"]' ) . first ( ) ;
161+
162+ // Wait for events to render
163+ await page . waitForTimeout ( 500 ) ;
164+
165+ // Open popup
166+ const event = timeline . locator ( '.timeline-event' ) . first ( ) ;
167+ await event . click ( ) ;
168+
169+ const popup = page . locator ( '.timeline-popup' ) ;
170+ await expect ( popup ) . toBeVisible ( ) ;
171+
172+ // Press Escape
173+ await page . keyboard . press ( 'Escape' ) ;
174+ await expect ( popup ) . not . toBeVisible ( ) ;
175+ } ) ;
176+
177+ test ( 'overview band shows tick markers' , async ( { page } ) => {
178+ const timeline = page . locator ( '[data-testid="timeline-container"]' ) . first ( ) ;
179+ const overviewBand = timeline . locator ( '.timeline-band--overview' ) ;
180+
181+ // Check overview markers are visible
182+ const markers = overviewBand . locator ( '.timeline-overview-marker' ) ;
183+
184+ // Wait for render
185+ await page . waitForTimeout ( 500 ) ;
186+
187+ const count = await markers . count ( ) ;
188+ expect ( count ) . toBeGreaterThanOrEqual ( 0 ) ; // May be 0 if events are outside viewport
189+ } ) ;
190+
191+ test ( 'keyboard navigation works' , async ( { page } ) => {
192+ const timeline = page . locator ( '[data-testid="timeline-container"]' ) . first ( ) ;
193+
194+ // Focus on the page
195+ await timeline . click ( ) ;
196+
197+ // Get initial scale label
198+ const initialLabel = await timeline . locator ( '.timeline-scale__label' ) . first ( ) . textContent ( ) ;
199+
200+ // Press right arrow multiple times
201+ for ( let i = 0 ; i < 5 ; i ++ ) {
202+ await page . keyboard . press ( 'ArrowRight' ) ;
203+ }
204+
205+ await page . waitForTimeout ( 100 ) ;
206+
207+ // Label should change
208+ const newLabel = await timeline . locator ( '.timeline-scale__label' ) . first ( ) . textContent ( ) ;
209+ expect ( newLabel ) . not . toBe ( initialLabel ) ;
210+ } ) ;
211+ } ) ;
212+
55213test . describe ( 'Library Import' , ( ) => {
56214 test ( 'Timeline component is imported from workspace package' , async ( { page } ) => {
57215 // This test verifies the workspace:* dependency works correctly
0 commit comments