Source: Layout/Header/__test__/clock.test.tsx

import { MessageChannel as NodeMessageChannel } from 'node:worker_threads';
import { act } from 'react';
import { IntlProvider } from 'react-intl';
import { DialogProvider } from '@/context/dialog';
import { messages } from '../../../../intl/translations';
import Header from '..';

// The suite-wide mock echoes its input, and these tests read the weekday, the day and the month the
// clock wrote. Only the real formatter writes them.
jest.mock('react-intl', () => jest.requireActual('react-intl'));

// `react-dom/server` resolves to its browser build under jsdom, and that build schedules through
// `MessageChannel`, which jsdom does not implement. Assigned here rather than in `jest.env.setup.js`
// because this is the only suite that server-renders; the other 87 have no use for it. It has to
// land before `react-dom/server` is loaded, which is why the two react-dom imports below are
// dynamic — static imports are hoisted above this line.
//
// Every channel is tracked so `afterAll` can close it. Node's real `MessageChannel` holds the event
// loop open, React's scheduler creates one when the module loads and never closes it, and `unref()`
// alone does not satisfy Jest's handle detector — the run still ends on "Jest did not exit one
// second after the test run has completed", which is a hanging worker in CI rather than a warning.
const openChannels: NodeMessageChannel[] = [];

class TrackedMessageChannel extends NodeMessageChannel {
    constructor() {
        super();
        openChannels.push(this);
    }
}

(globalThis as unknown as { MessageChannel: unknown }).MessageChannel = TrackedMessageChannel;

jest.mock('@/components/CryptoPrice', () => {
    const CryptoPrice = () => <div />;
    CryptoPrice.displayName = 'CryptoPrice';
    return CryptoPrice;
});

/**
 * The header clock, exercised the only way that can see the defect it had: server-render at one
 * instant, hydrate at another.
 *
 * `render()` from testing-library cannot catch this. It mounts on an empty container, so the text
 * it produces is the client's by construction and the test passes whatever the component does. The
 * pages here are statically generated, so the interesting gap is between the moment the HTML was
 * built and the moment a visitor loads it — days or weeks, not milliseconds.
 *
 * The bug: the four fields carried `suppressHydrationWarning`, which tells React to keep the
 * server's text and skip the DOM patch. After that React only rewrites a node whose value differs
 * from the PREVIOUS CLIENT RENDER, and the weekday, day and month are stable between one render and
 * the next, so the build's date stayed on screen until midnight — days late on a static page. The
 * time recovered on its own because the 60 s interval changes it.
 */

const BUILD_TIME = new Date('2025-12-14T21:07:00.000Z');
const VISIT_TIME = new Date('2026-01-15T10:30:00.000Z');

/** The four spans the clock renders, in order: weekday, day, month, time. */
function clockFields(container: HTMLElement): string[] {
    const clock = container.querySelector('.dateAndHour');
    if (!clock) throw new Error('no clock in the header');

    return [...clock.children].map((child) => child.textContent ?? '');
}

/** Held so `afterEach` can unmount it: the clock's interval keeps the process alive otherwise. */
let hydrated: { unmount: () => void } | null = null;

async function renderAtBuildThenHydrateAtVisit(): Promise<string[]> {
    const { renderToString } = await import('react-dom/server');
    const { hydrateRoot } = await import('react-dom/client');

    const tree = (
        <IntlProvider locale="en" messages={messages.en}>
            <DialogProvider>
                <Header />
            </DialogProvider>
        </IntlProvider>
    );

    jest.setSystemTime(BUILD_TIME);
    const serverHtml = renderToString(tree);

    const container = document.createElement('div');
    container.innerHTML = serverHtml;
    document.body.appendChild(container);

    // Guards the assertions below against a component that renders no clock at all: they are
    // written as "the visitor's value is present, the build's is not", and an empty header would
    // satisfy the second half on its own.
    expect(clockFields(container)).toHaveLength(4);

    jest.setSystemTime(VISIT_TIME);
    await act(async () => {
        hydrated = hydrateRoot(container, tree);
    });

    return clockFields(container);
}

describe('Header clock', () => {
    beforeEach(() => {
        // Only `Date` is faked. Faking the timers too hangs the suite: React's scheduler drives
        // hydration through `setTimeout`, so a fake clock that nobody advances leaves `act` waiting
        // for work that can never run. Nothing here needs to move time forward anyway — the defect
        // lives in the gap between the server render and the hydration, not in the 60 s interval.
        jest.useFakeTimers({
            doNotFake: [
                'hrtime',
                'nextTick',
                'performance',
                'queueMicrotask',
                'requestAnimationFrame',
                'cancelAnimationFrame',
                'requestIdleCallback',
                'cancelIdleCallback',
                'setImmediate',
                'clearImmediate',
                'setInterval',
                'clearInterval',
                'setTimeout',
                'clearTimeout',
            ],
        });
    });

    afterEach(() => {
        act(() => hydrated?.unmount());
        hydrated = null;
        jest.useRealTimers();
        document.body.innerHTML = '';
    });

    afterAll(() => {
        for (const channel of openChannels) {
            channel.port1.close();
            channel.port2.close();
        }
    });

    it('should show the visitor date after hydration, not the date the page was built', async () => {
        const fields = await renderAtBuildThenHydrateAtVisit();

        expect(fields).toContain('15');
        expect(fields).not.toContain('14');
    });

    it('should render no date on the server, so hydration has nothing to disagree with', async () => {
        const { renderToString } = await import('react-dom/server');

        jest.setSystemTime(BUILD_TIME);
        const container = document.createElement('div');
        container.innerHTML = renderToString(
            <IntlProvider locale="en" messages={messages.en}>
                <DialogProvider>
                    <Header />
                </DialogProvider>
            </IntlProvider>
        );

        // The mechanism the fix rests on: an empty clock server-side means the first client render
        // matches it, which is what makes `suppressHydrationWarning` unnecessary. Leave a value
        // here and the old defect comes straight back, because React would keep this text.
        expect(clockFields(container)).toEqual(['', '', '', '']);
    });

    it('should show the visitor month after hydration, not the month the page was built', async () => {
        const fields = await renderAtBuildThenHydrateAtVisit();

        // en-US short months for the two instants. Asserting on both directions rather than on the
        // exact string keeps this readable if the locale data shifts `Dec`/`Jan` spelling.
        expect(fields.join(' ')).toMatch(/Jan/);
        expect(fields.join(' ')).not.toMatch(/Dec/);
    });
});