|
6 | 6 | render, |
7 | 7 | screen, |
8 | 8 | waitFor, |
| 9 | + within, |
9 | 10 | } from '@testing-library/react'; |
10 | 11 | import { mockAllIsIntersecting } from 'react-intersection-observer/test-utils'; |
11 | 12 | import { QueryClient } from '@tanstack/react-query'; |
@@ -287,6 +288,64 @@ describe('FeedWidgetRail', () => { |
287 | 288 | }); |
288 | 289 | }); |
289 | 290 |
|
| 291 | + it('moves a widget with the keyboard and writes once, on drop', async () => { |
| 292 | + mockHero(); |
| 293 | + |
| 294 | + renderRail({ feedWidgets: { version: 1, widgets: twoWidgets } }); |
| 295 | + await revealWidgets(); |
| 296 | + |
| 297 | + await waitFor(() => |
| 298 | + expect(screen.getByText('First headline')).toBeInTheDocument(), |
| 299 | + ); |
| 300 | + fireEvent.click(screen.getByRole('button', { name: 'Customize' })); |
| 301 | + const widget = screen.getByRole('button', { |
| 302 | + name: 'Happening Now, Tall, position 2', |
| 303 | + }); |
| 304 | + fireEvent.keyDown(widget, { key: ' ' }); |
| 305 | + fireEvent.keyDown(widget, { key: 'ArrowLeft' }); |
| 306 | + expect(setSettings).not.toHaveBeenCalled(); |
| 307 | + |
| 308 | + fireEvent.keyDown( |
| 309 | + screen.getByRole('button', { name: 'Happening Now, Tall, position 1' }), |
| 310 | + { key: ' ' }, |
| 311 | + ); |
| 312 | + expect(setSettings).toHaveBeenCalledTimes(1); |
| 313 | + expect(setSettings).toHaveBeenCalledWith({ |
| 314 | + flags: { |
| 315 | + feedWidgets: { |
| 316 | + version: 1, |
| 317 | + widgets: [twoWidgets[1], twoWidgets[0]], |
| 318 | + }, |
| 319 | + }, |
| 320 | + }); |
| 321 | + }); |
| 322 | + |
| 323 | + it('drops a keyboard move on Escape without writing', async () => { |
| 324 | + mockHero(); |
| 325 | + |
| 326 | + renderRail({ feedWidgets: { version: 1, widgets: twoWidgets } }); |
| 327 | + await revealWidgets(); |
| 328 | + |
| 329 | + await waitFor(() => |
| 330 | + expect(screen.getByText('First headline')).toBeInTheDocument(), |
| 331 | + ); |
| 332 | + fireEvent.click(screen.getByRole('button', { name: 'Customize' })); |
| 333 | + const widget = screen.getByRole('button', { |
| 334 | + name: 'Happening Now, Tall, position 2', |
| 335 | + }); |
| 336 | + fireEvent.keyDown(widget, { key: ' ' }); |
| 337 | + fireEvent.keyDown(widget, { key: 'ArrowLeft' }); |
| 338 | + fireEvent.keyDown(window, { key: 'Escape' }); |
| 339 | + |
| 340 | + expect(setSettings).not.toHaveBeenCalled(); |
| 341 | + expect(widgetKinds()).toEqual([ |
| 342 | + FeedWidgetKind.TopStory, |
| 343 | + FeedWidgetKind.HappeningNow, |
| 344 | + ]); |
| 345 | + // Still editing: the Esc canceled the move, not the session. |
| 346 | + expect(screen.getByRole('button', { name: 'Done' })).toBeInTheDocument(); |
| 347 | + }); |
| 348 | + |
290 | 349 | it('places no slot when its column is off the first screen', async () => { |
291 | 350 | mockHero(); |
292 | 351 |
|
@@ -342,4 +401,35 @@ describe('FeedWidgetRail', () => { |
342 | 401 | screen.getByRole('button', { name: 'Customize' }), |
343 | 402 | ).toBeInTheDocument(); |
344 | 403 | }); |
| 404 | + |
| 405 | + it('lets a link through once a hold-to-edit session is over', async () => { |
| 406 | + mockHero(); |
| 407 | + |
| 408 | + renderRail(); |
| 409 | + await revealWidgets(); |
| 410 | + await waitFor(() => |
| 411 | + expect( |
| 412 | + screen.getByRole('heading', { name: 'First hero post' }), |
| 413 | + ).toBeInTheDocument(), |
| 414 | + ); |
| 415 | + const [widget] = screen.getAllByTestId('feedWidget'); |
| 416 | + |
| 417 | + jest.useFakeTimers(); |
| 418 | + fireEvent.pointerDown(widget, { pointerType: 'touch', button: 0 }); |
| 419 | + act(() => { |
| 420 | + jest.advanceTimersByTime(500); |
| 421 | + }); |
| 422 | + jest.useRealTimers(); |
| 423 | + // A long press ends without a click, so nothing clears the hold there. |
| 424 | + fireEvent.pointerUp(widget, { pointerType: 'touch' }); |
| 425 | + fireEvent.click(screen.getByRole('button', { name: 'Done' })); |
| 426 | + |
| 427 | + const [link] = within(widget).getAllByRole('link'); |
| 428 | + // The widget's capture handler stops a swallowed click before it reaches |
| 429 | + // the link itself. |
| 430 | + const onLinkClick = jest.fn(); |
| 431 | + link.addEventListener('click', onLinkClick); |
| 432 | + fireEvent.click(link); |
| 433 | + expect(onLinkClick).toHaveBeenCalled(); |
| 434 | + }); |
345 | 435 | }); |
0 commit comments