@@ -45,11 +45,11 @@ describe("ItemRow", () => {
4545 } ) ;
4646
4747 it ( "renders relative time for createdAt" , ( ) => {
48- render ( ( ) => < ItemRow { ...defaultProps } /> ) ;
48+ const { container } = render ( ( ) => < ItemRow { ...defaultProps } /> ) ;
4949 // Should show compact format like "2h"
50- const timeEl = screen . getByTitle ( `Created: ${ new Date ( defaultProps . createdAt ) . toLocaleString ( ) } `) ;
51- expect ( timeEl ) . toBeDefined ( ) ;
52- expect ( timeEl . textContent ) . toBe ( "2h" ) ;
50+ const timeEl = container . querySelector ( `time[datetime=" ${ defaultProps . createdAt } "] `) ;
51+ expect ( timeEl ) . not . toBeNull ( ) ;
52+ expect ( timeEl ! . textContent ) . toBe ( "2h" ) ;
5353 } ) ;
5454
5555 it ( "renders children slot when provided" , ( ) => {
@@ -177,8 +177,12 @@ describe("ItemRow", () => {
177177 it ( "shows both dates when updatedAt meaningfully differs from createdAt" , ( ) => {
178178 const { container } = render ( ( ) => < ItemRow { ...defaultProps } /> ) ;
179179 // createdAt=2h ago → "2h", updatedAt=30m ago → "30m"
180- expect ( screen . getByTitle ( `Created: ${ new Date ( defaultProps . createdAt ) . toLocaleString ( ) } ` ) . textContent ) . toBe ( "2h" ) ;
181- expect ( screen . getByTitle ( `Updated: ${ new Date ( defaultProps . updatedAt ) . toLocaleString ( ) } ` ) . textContent ) . toBe ( "30m" ) ;
180+ const created = container . querySelector ( `time[datetime="${ defaultProps . createdAt } "]` ) ;
181+ const updated = container . querySelector ( `time[datetime="${ defaultProps . updatedAt } "]` ) ;
182+ expect ( created ! . textContent ) . toBe ( "2h" ) ;
183+ expect ( created ! . getAttribute ( "title" ) ) . toBe ( `Created: ${ new Date ( defaultProps . createdAt ) . toLocaleString ( ) } ` ) ;
184+ expect ( updated ! . textContent ) . toBe ( "30m" ) ;
185+ expect ( updated ! . getAttribute ( "title" ) ) . toBe ( `Updated: ${ new Date ( defaultProps . updatedAt ) . toLocaleString ( ) } ` ) ;
182186 // Middle dot separator is a <span> with aria-hidden
183187 const dot = container . querySelector ( 'span[aria-hidden="true"]' ) ;
184188 expect ( dot ) . not . toBeNull ( ) ;
@@ -191,7 +195,7 @@ describe("ItemRow", () => {
191195 < ItemRow { ...defaultProps } createdAt = { sameDate } updatedAt = { sameDate } />
192196 ) ) ;
193197 expect ( container . querySelector ( 'span[aria-hidden="true"]' ) ) . toBeNull ( ) ;
194- expect ( screen . queryByTitle ( `Updated: ${ new Date ( sameDate ) . toLocaleString ( ) } ` ) ) . toBeNull ( ) ;
198+ expect ( container . querySelectorAll ( "time" ) . length ) . toBe ( 1 ) ;
195199 } ) ;
196200
197201 it ( "shows single date when updatedAt is within 60s of createdAt" , ( ) => {
@@ -202,9 +206,9 @@ describe("ItemRow", () => {
202206 updatedAt = "2026-03-30T11:59:30Z"
203207 />
204208 ) ) ;
205- // Only one time span — no dot separator span
209+ // Only one time element — no dot separator
206210 expect ( container . querySelector ( 'span[aria-hidden="true"]' ) ) . toBeNull ( ) ;
207- expect ( screen . queryByTitle ( `Updated: ${ new Date ( "2026-03-30T11:59:30Z ") . toLocaleString ( ) } ` ) ) . toBeNull ( ) ;
211+ expect ( container . querySelectorAll ( "time ") . length ) . toBe ( 1 ) ;
208212 } ) ;
209213
210214 it ( "shows single date when updatedAt is exactly 60s after createdAt" , ( ) => {
@@ -226,9 +230,18 @@ describe("ItemRow", () => {
226230 const { container } = render ( ( ) => (
227231 < ItemRow { ...defaultProps } createdAt = { createdAt } updatedAt = { updatedAt } />
228232 ) ) ;
229- // diff > 60s but both show "3d" — no dot separator span
233+ // diff > 60s but both show "3d" — no dot separator
234+ expect ( container . querySelector ( 'span[aria-hidden="true"]' ) ) . toBeNull ( ) ;
235+ expect ( container . querySelector ( `time[datetime="${ createdAt } "]` ) ! . textContent ) . toBe ( "3d" ) ;
236+ } ) ;
237+
238+ it ( "suppresses update display when dates are invalid" , ( ) => {
239+ const { container } = render ( ( ) => (
240+ < ItemRow { ...defaultProps } createdAt = "not-a-date" updatedAt = "also-invalid" />
241+ ) ) ;
230242 expect ( container . querySelector ( 'span[aria-hidden="true"]' ) ) . toBeNull ( ) ;
231- expect ( screen . getByTitle ( `Created: ${ new Date ( createdAt ) . toLocaleString ( ) } ` ) . textContent ) . toBe ( "3d" ) ;
243+ expect ( container . querySelectorAll ( "time" ) . length ) . toBe ( 1 ) ;
244+ expect ( container . querySelector ( "time" ) ! . textContent ) . toBe ( "" ) ;
232245 } ) ;
233246
234247 it ( "renders correct datetime attributes on time elements" , ( ) => {
@@ -240,35 +253,32 @@ describe("ItemRow", () => {
240253 } ) ;
241254
242255 it ( "shows verbose aria-label for created and updated spans" , ( ) => {
243- render ( ( ) => < ItemRow { ...defaultProps } /> ) ;
244- const createdSpan = screen . getByTitle ( `Created: ${ new Date ( defaultProps . createdAt ) . toLocaleString ( ) } `) ;
245- const updatedSpan = screen . getByTitle ( `Updated: ${ new Date ( defaultProps . updatedAt ) . toLocaleString ( ) } `) ;
246- expect ( createdSpan . getAttribute ( "aria-label" ) ) . toMatch ( / ^ C r e a t e d 2 h o u r s ? a g o $ / ) ;
247- expect ( updatedSpan . getAttribute ( "aria-label" ) ) . toMatch ( / ^ U p d a t e d 3 0 m i n u t e s ? a g o $ / ) ;
256+ const { container } = render ( ( ) => < ItemRow { ...defaultProps } /> ) ;
257+ const created = container . querySelector ( `time[datetime=" ${ defaultProps . createdAt } "] `) ;
258+ const updated = container . querySelector ( `time[datetime=" ${ defaultProps . updatedAt } "] `) ;
259+ expect ( created ! . getAttribute ( "aria-label" ) ) . toMatch ( / ^ C r e a t e d 2 h o u r s ? a g o $ / ) ;
260+ expect ( updated ! . getAttribute ( "aria-label" ) ) . toMatch ( / ^ U p d a t e d 3 0 m i n u t e s ? a g o $ / ) ;
248261 } ) ;
249262
250263 it ( "refreshTick forces time display update" , ( ) => {
251264 const [ tick , setTick ] = createSignal ( 0 ) ;
252265 let mockNow = MOCK_NOW ;
253- vi . spyOn ( Date , " now" ) . mockImplementation ( ( ) => mockNow ) ;
266+ vi . mocked ( Date . now ) . mockImplementation ( ( ) => mockNow ) ;
254267
255- // createdAt is 2h before MOCK_NOW → displays "2h"
256- // updatedAt is 30m before MOCK_NOW → displays "30m"
257- render ( ( ) => (
258- < ItemRow
259- { ...defaultProps }
260- refreshTick = { tick ( ) }
261- />
268+ const { container } = render ( ( ) => (
269+ < ItemRow { ...defaultProps } refreshTick = { tick ( ) } />
262270 ) ) ;
263- expect ( screen . getByTitle ( `Created: ${ new Date ( defaultProps . createdAt ) . toLocaleString ( ) } ` ) . textContent ) . toBe ( "2h" ) ;
264- expect ( screen . getByTitle ( `Updated: ${ new Date ( defaultProps . updatedAt ) . toLocaleString ( ) } ` ) . textContent ) . toBe ( "30m" ) ;
271+ const created = container . querySelector ( `time[datetime="${ defaultProps . createdAt } "]` ) ;
272+ const updated = container . querySelector ( `time[datetime="${ defaultProps . updatedAt } "]` ) ;
273+ expect ( created ! . textContent ) . toBe ( "2h" ) ;
274+ expect ( updated ! . textContent ) . toBe ( "30m" ) ;
265275
266276 // Advance mock time by 3 hours and bump refreshTick
267277 mockNow = MOCK_NOW + 3 * 60 * 60 * 1000 ;
268278 setTick ( 1 ) ;
269279
270- expect ( screen . getByTitle ( `Created: ${ new Date ( defaultProps . createdAt ) . toLocaleString ( ) } ` ) . textContent ) . toBe ( "5h" ) ;
280+ expect ( created ! . textContent ) . toBe ( "5h" ) ;
271281 // updatedAt was 30m before MOCK_NOW; after +3h it is 3h30m ago → Math.floor(210/60) = 3 → "3h"
272- expect ( screen . getByTitle ( `Updated: ${ new Date ( defaultProps . updatedAt ) . toLocaleString ( ) } ` ) . textContent ) . toBe ( "3h" ) ;
282+ expect ( updated ! . textContent ) . toBe ( "3h" ) ;
273283 } ) ;
274284} ) ;
0 commit comments