Source: StarPrompt/__test__/starPrompt.test.tsx

import { render, screen, fireEvent, act } from '@/test';
import StarPrompt, { STAR_PROMPT_STORAGE_KEY } from '..';

/**
 * The reading surface is a panel, not the window — see the component. jsdom reports 0 for every
 * layout box, so the geometry has to be stated: these numbers stand in for the blog body, measured
 * at 2409px of content in an 800px window.
 */
const panel = (scrollHeight = 2409, clientHeight = 800) => {
    const element = document.createElement('div');
    Object.defineProperty(element, 'scrollHeight', { value: scrollHeight, configurable: true });
    Object.defineProperty(element, 'clientHeight', { value: clientHeight, configurable: true });
    document.body.appendChild(element);
    return element;
};

const scrollTo = (element: HTMLElement, top: number) => {
    Object.defineProperty(element, 'scrollTop', { value: top, configurable: true });
    act(() => {
        element.dispatchEvent(new Event('scroll', { bubbles: false }));
    });
};

describe('StarPrompt', () => {
    beforeEach(() => {
        jest.useFakeTimers();
        window.localStorage.clear();
        document.body.innerHTML = '';
    });

    afterEach(() => {
        jest.useRealTimers();
    });

    it('appears once the visitor has read most of a panel', () => {
        const body = panel();
        render(<StarPrompt />);
        jest.setSystemTime(Date.now() + 60_000);

        scrollTo(body, 200);
        expect(screen.queryByTestId('star-prompt')).not.toBeInTheDocument();

        scrollTo(body, 1400);
        expect(screen.getByTestId('star-prompt')).toBeInTheDocument();
    });

    /** A flick of the wheel to the bottom is not reading, so the dwell has to hold too. */
    it('stays away when the panel was scrolled through immediately', () => {
        const body = panel();
        render(<StarPrompt />);

        scrollTo(body, 1600);

        expect(screen.queryByTestId('star-prompt')).not.toBeInTheDocument();
    });

    /** A short scroller is a list or a sidebar, not something that was read. */
    it('ignores scrollers too short to have been read', () => {
        const sidebar = panel(950, 800);
        render(<StarPrompt />);
        jest.setSystemTime(Date.now() + 60_000);

        scrollTo(sidebar, 150);

        expect(screen.queryByTestId('star-prompt')).not.toBeInTheDocument();
    });

    it('never asks twice, even if the visitor answers nothing', () => {
        const body = panel();
        const { unmount } = render(<StarPrompt />);
        jest.setSystemTime(Date.now() + 60_000);
        scrollTo(body, 1400);
        expect(screen.getByTestId('star-prompt')).toBeInTheDocument();
        expect(window.localStorage.getItem(STAR_PROMPT_STORAGE_KEY)).toBe('seen');
        unmount();

        render(<StarPrompt />);
        jest.setSystemTime(Date.now() + 60_000);
        scrollTo(body, 1400);

        expect(screen.queryByTestId('star-prompt')).not.toBeInTheDocument();
    });

    it('records the answer and closes', () => {
        const body = panel();
        render(<StarPrompt />);
        jest.setSystemTime(Date.now() + 60_000);
        scrollTo(body, 1400);

        fireEvent.click(screen.getByRole('button'));

        expect(screen.queryByTestId('star-prompt')).not.toBeInTheDocument();
        expect(window.localStorage.getItem(STAR_PROMPT_STORAGE_KEY)).toBe('dismissed');
    });

    it('closes on Escape', () => {
        const body = panel();
        render(<StarPrompt />);
        jest.setSystemTime(Date.now() + 60_000);
        scrollTo(body, 1400);

        fireEvent.keyDown(document, { key: 'Escape' });

        expect(screen.queryByTestId('star-prompt')).not.toBeInTheDocument();
    });
});