|
1 | 1 | import { fireEvent, render, screen } from "@testing-library/react"; |
2 | | -import { describe, expect, it } from "vitest"; |
| 2 | +import { act } from "react"; |
| 3 | +import { hydrateRoot } from "react-dom/client"; |
| 4 | +import { renderToString } from "react-dom/server"; |
| 5 | +import { describe, expect, it, vi } from "vitest"; |
3 | 6 | import { DomainIconLayer } from "./DomainIconLayer"; |
4 | 7 |
|
5 | 8 | function loadProbe(image: HTMLElement, width: number, height: number) { |
@@ -91,4 +94,64 @@ describe("DomainIconLayer", () => { |
91 | 94 | expect(second).toBe(first); |
92 | 95 | expect(second.className).toBe(first.className); |
93 | 96 | }); |
| 97 | + |
| 98 | + it("paints the layer when a hydrated probe is already complete without a load event", async () => { |
| 99 | + const names = ["complete", "naturalWidth", "naturalHeight"] as const; |
| 100 | + const saved = names.map( |
| 101 | + (name) => [name, Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, name)] as const, |
| 102 | + ); |
| 103 | + Object.defineProperty(HTMLImageElement.prototype, "complete", { |
| 104 | + configurable: true, |
| 105 | + get: () => true, |
| 106 | + }); |
| 107 | + Object.defineProperty(HTMLImageElement.prototype, "naturalWidth", { |
| 108 | + configurable: true, |
| 109 | + get: () => 32, |
| 110 | + }); |
| 111 | + Object.defineProperty(HTMLImageElement.prototype, "naturalHeight", { |
| 112 | + configurable: true, |
| 113 | + get: () => 32, |
| 114 | + }); |
| 115 | + |
| 116 | + const container = document.createElement("div"); |
| 117 | + document.body.append(container); |
| 118 | + const consoleError = vi.spyOn(console, "error").mockImplementation(() => {}); |
| 119 | + let root: ReturnType<typeof hydrateRoot> | undefined; |
| 120 | + |
| 121 | + try { |
| 122 | + container.innerHTML = renderToString( |
| 123 | + <DomainIconLayer src="https://icons.example.com/favicon.png" testId="domain-icon" />, |
| 124 | + ); |
| 125 | + |
| 126 | + expect(container.querySelector('[data-testid="domain-icon-probe"]')).not.toBeNull(); |
| 127 | + expect(container.querySelector('[data-testid="domain-icon"]')).toBeNull(); |
| 128 | + |
| 129 | + root = hydrateRoot( |
| 130 | + container, |
| 131 | + <DomainIconLayer src="https://icons.example.com/favicon.png" testId="domain-icon" />, |
| 132 | + ); |
| 133 | + |
| 134 | + await act(async () => undefined); |
| 135 | + |
| 136 | + expect(container.querySelector('[data-testid="domain-icon"]')).not.toBeNull(); |
| 137 | + expect(container.querySelector('[data-testid="domain-icon"]')).toHaveStyle({ |
| 138 | + backgroundImage: 'url("https://icons.example.com/favicon.png")', |
| 139 | + }); |
| 140 | + expect(consoleError).not.toHaveBeenCalled(); |
| 141 | + } finally { |
| 142 | + if (root) { |
| 143 | + const r = root; |
| 144 | + await act(async () => r.unmount()); |
| 145 | + } |
| 146 | + container.remove(); |
| 147 | + consoleError.mockRestore(); |
| 148 | + for (const [name, desc] of saved) { |
| 149 | + if (desc) { |
| 150 | + Object.defineProperty(HTMLImageElement.prototype, name, desc); |
| 151 | + } else { |
| 152 | + delete (HTMLImageElement.prototype as unknown as Record<string, unknown>)[name]; |
| 153 | + } |
| 154 | + } |
| 155 | + } |
| 156 | + }); |
94 | 157 | }); |
0 commit comments