preact-missing-hooks 4.8.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,46 @@
1
1
  /** @jsx h */
2
2
  import { h } from 'preact'
3
3
  import { render, waitFor } from '@testing-library/preact'
4
- import { getDeviceData, useDeviceData } from '../src/useDeviceData'
4
+ import { getDeviceData, parseUserAgent, useDeviceData } from '../src/useDeviceData'
5
+
6
+ const CHROME_WIN_UA =
7
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
8
+
9
+ describe('parseUserAgent', () => {
10
+ it('parses Chrome on Windows', () => {
11
+ const parsed = parseUserAgent(CHROME_WIN_UA)
12
+ expect(parsed.browser).toEqual({ name: 'Chrome', version: '120.0.0.0' })
13
+ expect(parsed.os).toEqual({ name: 'Windows', version: '10.0' })
14
+ })
15
+
16
+ it('parses Firefox on macOS', () => {
17
+ const ua =
18
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0'
19
+ const parsed = parseUserAgent(ua)
20
+ expect(parsed.browser.name).toBe('Firefox')
21
+ expect(parsed.browser.version).toBe('121.0')
22
+ expect(parsed.os.name).toBe('macOS')
23
+ expect(parsed.os.version).toBe('10.15')
24
+ })
25
+
26
+ it('parses Safari on iOS', () => {
27
+ const ua =
28
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1'
29
+ const parsed = parseUserAgent(ua)
30
+ expect(parsed.browser.name).toBe('Safari')
31
+ expect(parsed.browser.version).toBe('17.2')
32
+ expect(parsed.os.name).toBe('iOS')
33
+ expect(parsed.os.version).toBe('17.2')
34
+ })
35
+
36
+ it('parses Edge from user-agent', () => {
37
+ const ua =
38
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0'
39
+ const parsed = parseUserAgent(ua)
40
+ expect(parsed.browser.name).toBe('Edge')
41
+ expect(parsed.browser.version).toBe('120.0.0.0')
42
+ })
43
+ })
5
44
 
6
45
  describe('getDeviceData', () => {
7
46
  const originalNavigator = global.navigator
@@ -25,14 +64,16 @@ describe('getDeviceData', () => {
25
64
  const data = getDeviceData()
26
65
  vi.unstubAllGlobals()
27
66
  expect(data.userAgent).toBe('')
67
+ expect(data.browser).toEqual({ name: 'Unknown', version: '' })
68
+ expect(data.os).toEqual({ name: 'Unknown', version: '' })
28
69
  expect(data.online).toBe(true)
29
70
  expect(data.viewport.width).toBe(0)
30
71
  })
31
72
 
32
- it('reads navigator and screen fields', () => {
73
+ it('reads navigator, browser, OS, and screen fields', () => {
33
74
  Object.defineProperty(global, 'navigator', {
34
75
  value: {
35
- userAgent: 'TestAgent/1.0',
76
+ userAgent: CHROME_WIN_UA,
36
77
  language: 'en-US',
37
78
  languages: ['en-US', 'en'],
38
79
  platform: 'Win32',
@@ -57,25 +98,31 @@ describe('getDeviceData', () => {
57
98
  })
58
99
 
59
100
  const data = getDeviceData()
60
- expect(data.userAgent).toBe('TestAgent/1.0')
101
+ expect(data.userAgent).toBe(CHROME_WIN_UA)
102
+ expect(data.browser.name).toBe('Chrome')
103
+ expect(data.browser.version).toBe('120.0.0.0')
104
+ expect(data.os.name).toBe('Windows')
105
+ expect(data.os.version).toBe('10.0')
61
106
  expect(data.language).toBe('en-US')
62
- expect(data.languages).toEqual(['en-US', 'en'])
63
107
  expect(data.platform).toBe('Win32')
64
108
  expect(data.hardwareConcurrency).toBe(8)
65
- expect(data.deviceMemory).toBe(8)
66
109
  expect(data.screen.width).toBe(1920)
67
- expect(data.screen.height).toBe(1080)
68
110
  expect(data.touch).toBe(false)
69
111
  })
70
112
 
71
- it('includes userAgentData when navigator.userAgentData exists', () => {
113
+ it('prefers Client Hints brands over user-agent for browser name', () => {
72
114
  Object.defineProperty(global, 'navigator', {
73
115
  value: {
74
116
  ...originalNavigator,
117
+ userAgent:
118
+ 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 Chrome/120.0.0.0',
75
119
  userAgentData: {
76
120
  mobile: true,
77
121
  platform: 'Android',
78
- brands: [{ brand: 'Chromium', version: '120' }],
122
+ brands: [
123
+ { brand: 'Not A Brand', version: '99' },
124
+ { brand: 'Google Chrome', version: '120.0.0.0' },
125
+ ],
79
126
  },
80
127
  },
81
128
  writable: true,
@@ -83,8 +130,9 @@ describe('getDeviceData', () => {
83
130
 
84
131
  const data = getDeviceData()
85
132
  expect(data.userAgentData?.mobile).toBe(true)
86
- expect(data.userAgentData?.platform).toBe('Android')
87
- expect(data.userAgentData?.brands[0]?.brand).toBe('Chromium')
133
+ expect(data.browser.name).toBe('Google Chrome')
134
+ expect(data.browser.version).toBe('120.0.0.0')
135
+ expect(data.os.name).toBe('Android')
88
136
  })
89
137
  })
90
138
 
@@ -102,37 +150,89 @@ describe('useDeviceData', () => {
102
150
  window.removeEventListener = originalRemoveEventListener
103
151
  })
104
152
 
105
- it('returns device data from navigator', () => {
153
+ it('returns browser and OS from navigator user-agent', () => {
106
154
  Object.defineProperty(global, 'navigator', {
107
155
  value: {
108
156
  ...originalNavigator,
109
- userAgent: 'HookTest/2.0',
157
+ userAgent: CHROME_WIN_UA,
110
158
  language: 'fr',
111
159
  languages: ['fr'],
112
- platform: 'MacIntel',
160
+ platform: 'Win32',
113
161
  cookieEnabled: true,
114
162
  onLine: true,
115
- maxTouchPoints: 5,
116
- vendor: 'Apple',
163
+ maxTouchPoints: 0,
164
+ vendor: 'Google Inc.',
117
165
  },
118
166
  writable: true,
119
167
  })
120
168
 
121
169
  function TestComponent() {
122
- const device = useDeviceData({ includeBattery: false })
170
+ const device = useDeviceData({
171
+ includeBattery: false,
172
+ includeHighEntropy: false,
173
+ })
123
174
  return (
124
175
  <div>
125
- <span data-testid="ua">{device.userAgent}</span>
176
+ <span data-testid="browser">
177
+ {device.browser.name}:{device.browser.version}
178
+ </span>
179
+ <span data-testid="os">
180
+ {device.os.name}:{device.os.version}
181
+ </span>
126
182
  <span data-testid="lang">{device.language}</span>
127
- <span data-testid="touch">{String(device.touch)}</span>
128
183
  </div>
129
184
  )
130
185
  }
131
186
 
132
187
  const { getByTestId } = render(<TestComponent />)
133
- expect(getByTestId('ua').textContent).toBe('HookTest/2.0')
188
+ expect(getByTestId('browser').textContent).toBe('Chrome:120.0.0.0')
189
+ expect(getByTestId('os').textContent).toBe('Windows:10.0')
134
190
  expect(getByTestId('lang').textContent).toBe('fr')
135
- expect(getByTestId('touch').textContent).toBe('true')
191
+ })
192
+
193
+ it('enriches browser and OS version from getHighEntropyValues', async () => {
194
+ Object.defineProperty(global, 'navigator', {
195
+ value: {
196
+ ...originalNavigator,
197
+ userAgent: CHROME_WIN_UA,
198
+ userAgentData: {
199
+ platform: 'Windows',
200
+ brands: [{ brand: 'Google Chrome', version: '120.0.0.0' }],
201
+ getHighEntropyValues: vi.fn().mockResolvedValue({
202
+ platformVersion: '15.0.0',
203
+ fullVersionList: [
204
+ { brand: 'Google Chrome', version: '120.0.6099.130' },
205
+ ],
206
+ }),
207
+ },
208
+ },
209
+ writable: true,
210
+ })
211
+
212
+ function TestComponent() {
213
+ const device = useDeviceData({
214
+ includeBattery: false,
215
+ includeHighEntropy: true,
216
+ })
217
+ return (
218
+ <div>
219
+ <span data-testid="browser">
220
+ {device.browser.name}:{device.browser.version}
221
+ </span>
222
+ <span data-testid="os">
223
+ {device.os.name}:{device.os.version}
224
+ </span>
225
+ </div>
226
+ )
227
+ }
228
+
229
+ const { getByTestId } = render(<TestComponent />)
230
+ await waitFor(() => {
231
+ expect(getByTestId('browser').textContent).toBe(
232
+ 'Google Chrome:120.0.6099.130',
233
+ )
234
+ expect(getByTestId('os').textContent).toBe('Windows:15.0.0')
235
+ })
136
236
  })
137
237
 
138
238
  it('updates viewport on resize', async () => {
@@ -152,15 +252,16 @@ describe('useDeviceData', () => {
152
252
  })
153
253
 
154
254
  Object.defineProperty(global, 'navigator', {
155
- value: { ...originalNavigator, onLine: true },
255
+ value: { ...originalNavigator, onLine: true, userAgent: CHROME_WIN_UA },
156
256
  writable: true,
157
257
  })
158
258
 
159
259
  function TestComponent() {
160
- const device = useDeviceData({ includeBattery: false })
161
- return (
162
- <span data-testid="vw">{device.viewport.width}</span>
163
- )
260
+ const device = useDeviceData({
261
+ includeBattery: false,
262
+ includeHighEntropy: false,
263
+ })
264
+ return <span data-testid="vw">{device.viewport.width}</span>
164
265
  }
165
266
 
166
267
  const { getByTestId } = render(<TestComponent />)
@@ -182,6 +283,7 @@ describe('useDeviceData', () => {
182
283
  value: {
183
284
  ...originalNavigator,
184
285
  onLine: true,
286
+ userAgent: CHROME_WIN_UA,
185
287
  getBattery: () =>
186
288
  Promise.resolve({ charging: true, level: 0.75 }),
187
289
  },
@@ -191,6 +293,7 @@ describe('useDeviceData', () => {
191
293
  function TestComponent() {
192
294
  const device = useDeviceData({
193
295
  includeBattery: true,
296
+ includeHighEntropy: false,
194
297
  batteryPollIntervalMs: 0,
195
298
  })
196
299
  return (