@volley/vwr-loader 1.0.6 → 1.2.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.
Files changed (102) hide show
  1. package/README.md +7 -8
  2. package/dist/__mocks__/@datadog/browser-logs.d.ts +58 -0
  3. package/dist/__mocks__/@datadog/browser-logs.d.ts.map +1 -0
  4. package/dist/__mocks__/@datadog/browser-logs.js +34 -0
  5. package/dist/__mocks__/@datadog/browser-logs.js.map +1 -0
  6. package/dist/__mocks__/@datadog/browser-rum.d.ts +25 -0
  7. package/dist/__mocks__/@datadog/browser-rum.d.ts.map +1 -0
  8. package/dist/__mocks__/@datadog/browser-rum.js +25 -0
  9. package/dist/__mocks__/@datadog/browser-rum.js.map +1 -0
  10. package/dist/amplitudeFlagFetcher.d.ts +8 -3
  11. package/dist/amplitudeFlagFetcher.d.ts.map +1 -1
  12. package/dist/amplitudeFlagFetcher.js +14 -12
  13. package/dist/amplitudeFlagFetcher.js.map +1 -1
  14. package/dist/assets/profiler-BRmTNL9s.js +2 -0
  15. package/dist/assets/profiler-BRmTNL9s.js.map +1 -0
  16. package/dist/assets/startRecording-CuFdVhxx.js +3 -0
  17. package/dist/assets/startRecording-CuFdVhxx.js.map +1 -0
  18. package/dist/cli.js +47 -34
  19. package/dist/cli.js.map +1 -1
  20. package/dist/datadog.d.ts +19 -0
  21. package/dist/datadog.d.ts.map +1 -0
  22. package/dist/datadog.js +39 -0
  23. package/dist/datadog.js.map +1 -0
  24. package/dist/envDefaults.d.ts.map +1 -1
  25. package/dist/envDefaults.js +27 -12
  26. package/dist/envDefaults.js.map +1 -1
  27. package/dist/errors/InitializationError.d.ts +74 -0
  28. package/dist/errors/InitializationError.d.ts.map +1 -0
  29. package/dist/errors/InitializationError.js +77 -0
  30. package/dist/errors/InitializationError.js.map +1 -0
  31. package/dist/errors/ensureError.d.ts +2 -0
  32. package/dist/errors/ensureError.d.ts.map +1 -0
  33. package/dist/errors/ensureError.js +7 -0
  34. package/dist/errors/ensureError.js.map +1 -0
  35. package/dist/errors/index.d.ts +12 -0
  36. package/dist/errors/index.d.ts.map +1 -0
  37. package/dist/errors/index.js +10 -0
  38. package/dist/errors/index.js.map +1 -0
  39. package/dist/getDeviceId.d.ts +1 -32
  40. package/dist/getDeviceId.d.ts.map +1 -1
  41. package/dist/getDeviceId.js +18 -16
  42. package/dist/getDeviceId.js.map +1 -1
  43. package/dist/getEnvironment.d.ts +3 -4
  44. package/dist/getEnvironment.d.ts.map +1 -1
  45. package/dist/getEnvironment.js +11 -6
  46. package/dist/getEnvironment.js.map +1 -1
  47. package/dist/getShellVersion.d.ts +2 -20
  48. package/dist/getShellVersion.d.ts.map +1 -1
  49. package/dist/getShellVersion.js +41 -8
  50. package/dist/getShellVersion.js.map +1 -1
  51. package/dist/index.d.ts +0 -1
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/loadVwr.d.ts +4 -4
  55. package/dist/loadVwr.d.ts.map +1 -1
  56. package/dist/loadVwr.js +151 -37
  57. package/dist/loadVwr.js.map +1 -1
  58. package/dist/logger.d.ts +11 -6
  59. package/dist/logger.d.ts.map +1 -1
  60. package/dist/logger.js +107 -4
  61. package/dist/logger.js.map +1 -1
  62. package/dist/main.js +9 -1
  63. package/dist/main.js.map +1 -1
  64. package/dist/test-setup.d.ts +2 -0
  65. package/dist/test-setup.d.ts.map +1 -0
  66. package/dist/test-setup.js +5 -0
  67. package/dist/test-setup.js.map +1 -0
  68. package/dist/types.d.ts +87 -0
  69. package/dist/types.d.ts.map +1 -0
  70. package/dist/types.js +5 -0
  71. package/dist/types.js.map +1 -0
  72. package/dist/vwrConfig.d.ts +14 -2
  73. package/dist/vwrConfig.d.ts.map +1 -1
  74. package/dist/vwrConfig.js +74 -29
  75. package/dist/vwrConfig.js.map +1 -1
  76. package/package.json +59 -53
  77. package/src/__mocks__/@datadog/browser-logs.ts +35 -0
  78. package/src/__mocks__/@datadog/browser-rum.ts +27 -0
  79. package/src/amplitudeFlagFetcher.test.ts +28 -34
  80. package/src/amplitudeFlagFetcher.ts +22 -14
  81. package/src/datadog.ts +55 -0
  82. package/src/envDefaults.ts +32 -13
  83. package/src/errors/InitializationError.ts +105 -0
  84. package/src/errors/ensureError.ts +6 -0
  85. package/src/errors/index.ts +16 -0
  86. package/src/getDeviceId.test.ts +29 -23
  87. package/src/getDeviceId.ts +25 -72
  88. package/src/getEnvironment.test.ts +68 -108
  89. package/src/getEnvironment.ts +12 -8
  90. package/src/getShellVersion.test.ts +134 -9
  91. package/src/getShellVersion.ts +44 -26
  92. package/src/index.ts +0 -1
  93. package/src/loadVwr.test.ts +394 -0
  94. package/src/loadVwr.ts +243 -58
  95. package/src/logger.ts +118 -11
  96. package/src/main.test.ts +157 -0
  97. package/src/main.ts +79 -11
  98. package/src/test-setup.ts +5 -0
  99. package/src/types.ts +88 -0
  100. package/src/vite-env.d.ts +2 -0
  101. package/src/vwrConfig.test.ts +104 -34
  102. package/src/vwrConfig.ts +115 -37
@@ -1,19 +1,21 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
2
2
 
3
3
  import { type Environment, getEnvironment } from "./getEnvironment"
4
+ import { logger } from "./logger"
4
5
 
5
- describe("getEnvironment", () => {
6
- const mockLogger = {
6
+ vi.mock("./logger", () => ({
7
+ logger: {
7
8
  info: vi.fn(),
8
9
  warn: vi.fn(),
9
10
  error: vi.fn(),
10
- }
11
+ debug: vi.fn(),
12
+ child: vi.fn(),
13
+ },
14
+ }))
11
15
 
16
+ describe("getEnvironment", () => {
12
17
  beforeEach(() => {
13
18
  vi.clearAllMocks()
14
- mockLogger.info.mockClear()
15
- mockLogger.warn.mockClear()
16
- mockLogger.error.mockClear()
17
19
  // Clean up window properties
18
20
  delete (window as any).Capacitor
19
21
  })
@@ -42,15 +44,11 @@ describe("getEnvironment", () => {
42
44
  configurable: true,
43
45
  })
44
46
 
45
- const result = await getEnvironment(
46
- "FIRE_TV",
47
- undefined,
48
- mockLogger
49
- )
47
+ const result = await getEnvironment("FIRE_TV", undefined)
50
48
 
51
49
  expect(result.environment).toBe("dev")
52
50
  expect(result.source).toBe("native")
53
- expect(mockLogger.info).toHaveBeenCalledWith(
51
+ expect(logger.info).toHaveBeenCalledWith(
54
52
  expect.stringContaining(
55
53
  'Environment: "dev" (source: native BuildConfig.ENVIRONMENT="development")'
56
54
  )
@@ -74,11 +72,7 @@ describe("getEnvironment", () => {
74
72
  configurable: true,
75
73
  })
76
74
 
77
- const result = await getEnvironment(
78
- "FIRE_TV",
79
- undefined,
80
- mockLogger
81
- )
75
+ const result = await getEnvironment("FIRE_TV", undefined)
82
76
 
83
77
  expect(result.environment).toBe("prod")
84
78
  expect(result.source).toBe("native")
@@ -101,11 +95,30 @@ describe("getEnvironment", () => {
101
95
  configurable: true,
102
96
  })
103
97
 
104
- const result = await getEnvironment(
105
- "FIRE_TV",
106
- undefined,
107
- mockLogger
108
- )
98
+ const result = await getEnvironment("FIRE_TV", undefined)
99
+
100
+ expect(result.environment).toBe("staging")
101
+ expect(result.source).toBe("native")
102
+ })
103
+
104
+ it("should map legacy 'qa' to 'staging'", async () => {
105
+ Object.defineProperty(window, "Capacitor", {
106
+ value: {
107
+ Plugins: {
108
+ DeviceInfo: {
109
+ getNativeShellAppEnvironment: vi
110
+ .fn()
111
+ .mockResolvedValue({
112
+ environment: "qa",
113
+ }),
114
+ },
115
+ },
116
+ },
117
+ writable: true,
118
+ configurable: true,
119
+ })
120
+
121
+ const result = await getEnvironment("FIRE_TV", undefined)
109
122
 
110
123
  expect(result.environment).toBe("staging")
111
124
  expect(result.source).toBe("native")
@@ -128,11 +141,7 @@ describe("getEnvironment", () => {
128
141
  configurable: true,
129
142
  })
130
143
 
131
- const result = await getEnvironment(
132
- "FIRE_TV",
133
- undefined,
134
- mockLogger
135
- )
144
+ const result = await getEnvironment("FIRE_TV", undefined)
136
145
 
137
146
  expect(result.environment).toBe("dev")
138
147
  expect(result.source).toBe("native")
@@ -155,11 +164,7 @@ describe("getEnvironment", () => {
155
164
  configurable: true,
156
165
  })
157
166
 
158
- const result = await getEnvironment(
159
- "FIRE_TV",
160
- undefined,
161
- mockLogger
162
- )
167
+ const result = await getEnvironment("FIRE_TV", undefined)
163
168
 
164
169
  expect(result.environment).toBe("dev")
165
170
  expect(result.source).toBe("native")
@@ -167,15 +172,11 @@ describe("getEnvironment", () => {
167
172
 
168
173
  it("should fallback to build-time when native plugin unavailable", async () => {
169
174
  // No Capacitor plugin available
170
- const result = await getEnvironment(
171
- "FIRE_TV",
172
- "staging",
173
- mockLogger
174
- )
175
+ const result = await getEnvironment("FIRE_TV", "staging")
175
176
 
176
177
  expect(result.environment).toBe("staging")
177
178
  expect(result.source).toBe("build-time")
178
- expect(mockLogger.warn).toHaveBeenCalledWith(
179
+ expect(logger.warn).toHaveBeenCalledWith(
179
180
  expect.stringContaining(
180
181
  "Failed to read environment from native, falling back to build-time"
181
182
  )
@@ -199,12 +200,12 @@ describe("getEnvironment", () => {
199
200
  configurable: true,
200
201
  })
201
202
 
202
- const result = await getEnvironment("FIRE_TV", "prod", mockLogger)
203
+ const result = await getEnvironment("FIRE_TV", "prod")
203
204
 
204
205
  expect(result.environment).toBe("prod")
205
206
  expect(result.source).toBe("build-time")
206
- expect(mockLogger.error).toHaveBeenCalled()
207
- expect(mockLogger.warn).toHaveBeenCalledWith(
207
+ expect(logger.error).toHaveBeenCalled()
208
+ expect(logger.warn).toHaveBeenCalledWith(
208
209
  expect.stringContaining("falling back to build-time")
209
210
  )
210
211
  })
@@ -226,11 +227,11 @@ describe("getEnvironment", () => {
226
227
  configurable: true,
227
228
  })
228
229
 
229
- const result = await getEnvironment("FIRE_TV", "dev", mockLogger)
230
+ const result = await getEnvironment("FIRE_TV", "dev")
230
231
 
231
232
  expect(result.environment).toBe("dev")
232
233
  expect(result.source).toBe("build-time")
233
- expect(mockLogger.error).toHaveBeenCalledWith(
234
+ expect(logger.error).toHaveBeenCalledWith(
234
235
  expect.stringContaining(
235
236
  "DeviceInfo.getNativeShellAppEnvironment returned empty"
236
237
  )
@@ -239,15 +240,11 @@ describe("getEnvironment", () => {
239
240
 
240
241
  it("should fallback to default when both native and build-time fail", async () => {
241
242
  // No Capacitor plugin, no build-time env
242
- const result = await getEnvironment(
243
- "FIRE_TV",
244
- undefined,
245
- mockLogger
246
- )
243
+ const result = await getEnvironment("FIRE_TV", undefined)
247
244
 
248
245
  expect(result.environment).toBe("dev")
249
246
  expect(result.source).toBe("default")
250
- expect(mockLogger.warn).toHaveBeenCalledWith(
247
+ expect(logger.warn).toHaveBeenCalledWith(
251
248
  expect.stringContaining(
252
249
  'Environment: "dev" (source: default fallback'
253
250
  )
@@ -271,16 +268,8 @@ describe("getEnvironment", () => {
271
268
  configurable: true,
272
269
  })
273
270
 
274
- const result1 = await getEnvironment(
275
- "fire_tv",
276
- undefined,
277
- mockLogger
278
- )
279
- const result2 = await getEnvironment(
280
- "FIRE_TV",
281
- undefined,
282
- mockLogger
283
- )
271
+ const result1 = await getEnvironment("fire_tv", undefined)
272
+ const result2 = await getEnvironment("FIRE_TV", undefined)
284
273
 
285
274
  expect(result1.environment).toBe("prod")
286
275
  expect(result2.environment).toBe("prod")
@@ -293,15 +282,11 @@ describe("getEnvironment", () => {
293
282
  platforms.forEach((platform) => {
294
283
  describe(`${platform}`, () => {
295
284
  it("should use build-time environment when provided", async () => {
296
- const result = await getEnvironment(
297
- platform,
298
- "staging",
299
- mockLogger
300
- )
285
+ const result = await getEnvironment(platform, "staging")
301
286
 
302
287
  expect(result.environment).toBe("staging")
303
288
  expect(result.source).toBe("build-time")
304
- expect(mockLogger.info).toHaveBeenCalledWith(
289
+ expect(logger.info).toHaveBeenCalledWith(
305
290
  expect.stringContaining(
306
291
  'Environment: "staging" (source: build-time CLI injection)'
307
292
  )
@@ -309,48 +294,32 @@ describe("getEnvironment", () => {
309
294
  })
310
295
 
311
296
  it("should use build-time 'prod' environment", async () => {
312
- const result = await getEnvironment(
313
- platform,
314
- "prod",
315
- mockLogger
316
- )
297
+ const result = await getEnvironment(platform, "prod")
317
298
 
318
299
  expect(result.environment).toBe("prod")
319
300
  expect(result.source).toBe("build-time")
320
301
  })
321
302
 
322
303
  it("should use build-time 'dev' environment", async () => {
323
- const result = await getEnvironment(
324
- platform,
325
- "dev",
326
- mockLogger
327
- )
304
+ const result = await getEnvironment(platform, "dev")
328
305
 
329
306
  expect(result.environment).toBe("dev")
330
307
  expect(result.source).toBe("build-time")
331
308
  })
332
309
 
333
310
  it("should use build-time 'local' environment", async () => {
334
- const result = await getEnvironment(
335
- platform,
336
- "local",
337
- mockLogger
338
- )
311
+ const result = await getEnvironment(platform, "local")
339
312
 
340
313
  expect(result.environment).toBe("local")
341
314
  expect(result.source).toBe("build-time")
342
315
  })
343
316
 
344
317
  it("should fallback to default when build-time not provided", async () => {
345
- const result = await getEnvironment(
346
- platform,
347
- undefined,
348
- mockLogger
349
- )
318
+ const result = await getEnvironment(platform, undefined)
350
319
 
351
320
  expect(result.environment).toBe("dev")
352
321
  expect(result.source).toBe("default")
353
- expect(mockLogger.warn).toHaveBeenCalledWith(
322
+ expect(logger.warn).toHaveBeenCalledWith(
354
323
  expect.stringContaining(
355
324
  'Environment: "dev" (source: default fallback'
356
325
  )
@@ -358,11 +327,7 @@ describe("getEnvironment", () => {
358
327
  })
359
328
 
360
329
  it("should fallback to default when build-time is invalid", async () => {
361
- const result = await getEnvironment(
362
- platform,
363
- "invalid-env",
364
- mockLogger
365
- )
330
+ const result = await getEnvironment(platform, "invalid-env")
366
331
 
367
332
  expect(result.environment).toBe("dev")
368
333
  expect(result.source).toBe("default")
@@ -373,16 +338,10 @@ describe("getEnvironment", () => {
373
338
 
374
339
  describe("Environment Validation", () => {
375
340
  it("should accept valid environments", async () => {
376
- const validEnvs: Environment[] = [
377
- "local",
378
- "dev",
379
- "qa",
380
- "staging",
381
- "prod",
382
- ]
341
+ const validEnvs: Environment[] = ["local", "dev", "staging", "prod"]
383
342
 
384
343
  for (const env of validEnvs) {
385
- const result = await getEnvironment("WEB", env, mockLogger)
344
+ const result = await getEnvironment("WEB", env)
386
345
  expect(result.environment).toBe(env)
387
346
  expect(result.source).toBe("build-time")
388
347
  }
@@ -392,13 +351,14 @@ describe("getEnvironment", () => {
392
351
  const invalidEnvs = [
393
352
  "development",
394
353
  "production",
354
+ "qa",
395
355
  "test",
396
356
  "",
397
357
  "random",
398
358
  ]
399
359
 
400
360
  for (const env of invalidEnvs) {
401
- const result = await getEnvironment("WEB", env, mockLogger)
361
+ const result = await getEnvironment("WEB", env)
402
362
  expect(result.environment).toBe("dev")
403
363
  expect(result.source).toBe("default")
404
364
  }
@@ -423,33 +383,33 @@ describe("getEnvironment", () => {
423
383
  configurable: true,
424
384
  })
425
385
 
426
- await getEnvironment("FIRE_TV", undefined, mockLogger)
386
+ await getEnvironment("FIRE_TV", undefined)
427
387
 
428
- expect(mockLogger.info).toHaveBeenCalledWith(
388
+ expect(logger.info).toHaveBeenCalledWith(
429
389
  '[Shell] Environment: "prod" (source: native BuildConfig.ENVIRONMENT="production")'
430
390
  )
431
391
  })
432
392
 
433
393
  it("should log build-time environment source", async () => {
434
- await getEnvironment("SAMSUNG_TV", "staging", mockLogger)
394
+ await getEnvironment("SAMSUNG_TV", "staging")
435
395
 
436
- expect(mockLogger.info).toHaveBeenCalledWith(
396
+ expect(logger.info).toHaveBeenCalledWith(
437
397
  '[Shell] Environment: "staging" (source: build-time CLI injection)'
438
398
  )
439
399
  })
440
400
 
441
401
  it("should warn when falling back to default", async () => {
442
- await getEnvironment("LG_TV", undefined, mockLogger)
402
+ await getEnvironment("LG_TV", undefined)
443
403
 
444
- expect(mockLogger.warn).toHaveBeenCalledWith(
404
+ expect(logger.warn).toHaveBeenCalledWith(
445
405
  '[Shell] Environment: "dev" (source: default fallback - no native or build-time env found)'
446
406
  )
447
407
  })
448
408
 
449
409
  it("should warn when native fails and falls back", async () => {
450
- await getEnvironment("FIRE_TV", "prod", mockLogger)
410
+ await getEnvironment("FIRE_TV", "prod")
451
411
 
452
- expect(mockLogger.warn).toHaveBeenCalledWith(
412
+ expect(logger.warn).toHaveBeenCalledWith(
453
413
  "[Shell] Failed to read environment from native, falling back to build-time"
454
414
  )
455
415
  })
@@ -1,6 +1,6 @@
1
- import { defaultLogger, type Logger } from "./logger"
1
+ import { logger } from "./logger"
2
2
 
3
- export type Environment = "local" | "dev" | "qa" | "staging" | "prod"
3
+ export type Environment = "local" | "dev" | "staging" | "prod"
4
4
  export type EnvironmentSource = "native" | "build-time" | "default"
5
5
 
6
6
  export interface EnvironmentResult {
@@ -19,21 +19,20 @@ export interface EnvironmentResult {
19
19
  * This ensures production APKs cannot accidentally run dev VWR,
20
20
  * since the native Android build variant determines the environment.
21
21
  *
22
- * @param platform - Platform identifier ('FIRE_TV', 'SAMSUNG_TV', 'LG_TV', 'MOBILE', 'WEB')
22
+ * @param platform - Platform identifier ('FIRE_TV', 'SAMSUNG_TV', 'LG_TV', 'ANDROID_MOBILE', 'IOS_MOBILE', 'WEB')
23
23
  * @param buildTimeEnv - Optional build-time injected environment (from CLI)
24
24
  * @param logger - Optional logger for reporting
25
25
  * @returns Environment result with source information for logging
26
26
  */
27
27
  export async function getEnvironment(
28
28
  platform: string,
29
- buildTimeEnv: string | undefined,
30
- logger: Logger = defaultLogger
29
+ buildTimeEnv: string | undefined
31
30
  ): Promise<EnvironmentResult> {
32
31
  const normalizedPlatform = platform.toUpperCase()
33
32
 
34
33
  // For Fire TV, try to read from native first (safest - prevents env mismatch)
35
34
  if (normalizedPlatform === "FIRE_TV") {
36
- const nativeEnv = await getFireTVEnvironment(logger)
35
+ const nativeEnv = await getFireTVEnvironment()
37
36
  if (nativeEnv) {
38
37
  const mapped = mapNativeEnvironment(nativeEnv)
39
38
  logger.info(
@@ -72,7 +71,7 @@ export async function getEnvironment(
72
71
  /**
73
72
  * Read environment from Fire TV native shell via Capacitor plugin.
74
73
  */
75
- async function getFireTVEnvironment(logger: Logger): Promise<string | null> {
74
+ async function getFireTVEnvironment(): Promise<string | null> {
76
75
  try {
77
76
  if (
78
77
  !window.Capacitor?.Plugins?.DeviceInfo?.getNativeShellAppEnvironment
@@ -113,12 +112,17 @@ async function getFireTVEnvironment(logger: Logger): Promise<string | null> {
113
112
  * - "dev"
114
113
  * - "staging"
115
114
  * - "prod"
115
+ *
116
+ * Note: Legacy "qa" values are mapped to "staging".
116
117
  */
117
118
  function mapNativeEnvironment(nativeEnv: string): Environment {
118
119
  const normalized = nativeEnv.toLowerCase()
119
120
  switch (normalized) {
120
121
  case "development":
121
122
  return "dev"
123
+ case "qa":
124
+ // Legacy: map qa to staging
125
+ return "staging"
122
126
  case "staging":
123
127
  return "staging"
124
128
  case "production":
@@ -133,7 +137,7 @@ function mapNativeEnvironment(nativeEnv: string): Environment {
133
137
  }
134
138
 
135
139
  function isValidEnvironment(env: string): boolean {
136
- return ["local", "dev", "qa", "staging", "prod"].includes(env)
140
+ return ["local", "dev", "staging", "prod"].includes(env)
137
141
  }
138
142
 
139
143
  // Note: Window.Capacitor type is declared in getDeviceId.ts
@@ -1,6 +1,17 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
2
2
 
3
3
  import { getShellVersion } from "./getShellVersion"
4
+ import { logger } from "./logger"
5
+
6
+ vi.mock("./logger", () => ({
7
+ logger: {
8
+ info: vi.fn(),
9
+ warn: vi.fn(),
10
+ error: vi.fn(),
11
+ debug: vi.fn(),
12
+ child: vi.fn(),
13
+ },
14
+ }))
4
15
 
5
16
  describe("getShellVersion", () => {
6
17
  const originalLocation = window.location
@@ -220,11 +231,130 @@ describe("getShellVersion", () => {
220
231
  })
221
232
  })
222
233
 
223
- describe("Mobile", () => {
224
- it("returns unknown (not yet implemented)", async () => {
225
- const version = await getShellVersion("MOBILE")
234
+ describe("Mobile (iOS)", () => {
235
+ afterEach(() => {
236
+ delete (window as any).iosAppContext
237
+ })
238
+
239
+ it("returns version from iosAppContext.appVersion", async () => {
240
+ ;(window as any).iosAppContext = {
241
+ appVersion: "2.1.0",
242
+ }
243
+
244
+ const version = await getShellVersion("IOS_MOBILE")
245
+
246
+ expect(version).toBe("2.1.0")
247
+ })
248
+
249
+ it("returns unknown when iosAppContext is not available", async () => {
250
+ const consoleWarnSpy = vi
251
+ .spyOn(console, "warn")
252
+ .mockImplementation(() => {})
253
+
254
+ const version = await getShellVersion("IOS_MOBILE")
226
255
 
227
256
  expect(version).toBe("unknown")
257
+
258
+ consoleWarnSpy.mockRestore()
259
+ })
260
+
261
+ it("returns unknown when appVersion is empty", async () => {
262
+ const consoleWarnSpy = vi
263
+ .spyOn(console, "warn")
264
+ .mockImplementation(() => {})
265
+
266
+ ;(window as any).iosAppContext = {
267
+ appVersion: " ",
268
+ }
269
+
270
+ const version = await getShellVersion("IOS_MOBILE")
271
+
272
+ expect(version).toBe("unknown")
273
+
274
+ consoleWarnSpy.mockRestore()
275
+ })
276
+
277
+ it("is case insensitive", async () => {
278
+ ;(window as any).iosAppContext = {
279
+ appVersion: "2.2.0",
280
+ }
281
+
282
+ const version = await getShellVersion("ios_mobile")
283
+
284
+ expect(version).toBe("2.2.0")
285
+ })
286
+ })
287
+
288
+ describe("Mobile (Android)", () => {
289
+ afterEach(() => {
290
+ delete (window as any).androidAppContext
291
+ })
292
+
293
+ it("returns version from androidAppContext.appVersion", async () => {
294
+ ;(window as any).androidAppContext = {
295
+ appVersion: "3.1.0",
296
+ }
297
+
298
+ const version = await getShellVersion("ANDROID_MOBILE")
299
+
300
+ expect(version).toBe("3.1.0")
301
+ })
302
+
303
+ it("returns unknown when androidAppContext is not available", async () => {
304
+ const consoleWarnSpy = vi
305
+ .spyOn(console, "warn")
306
+ .mockImplementation(() => {})
307
+
308
+ const version = await getShellVersion("ANDROID_MOBILE")
309
+
310
+ expect(version).toBe("unknown")
311
+
312
+ consoleWarnSpy.mockRestore()
313
+ })
314
+
315
+ it("returns unknown when appVersion is empty", async () => {
316
+ const consoleWarnSpy = vi
317
+ .spyOn(console, "warn")
318
+ .mockImplementation(() => {})
319
+
320
+ ;(window as any).androidAppContext = {
321
+ appVersion: "",
322
+ }
323
+
324
+ const version = await getShellVersion("ANDROID_MOBILE")
325
+
326
+ expect(version).toBe("unknown")
327
+
328
+ consoleWarnSpy.mockRestore()
329
+ })
330
+
331
+ it("is case insensitive", async () => {
332
+ ;(window as any).androidAppContext = {
333
+ appVersion: "3.2.0",
334
+ }
335
+
336
+ const version = await getShellVersion("android_mobile")
337
+
338
+ expect(version).toBe("3.2.0")
339
+ })
340
+
341
+ it("prefers iOS context when both are available", async () => {
342
+ ;(window as any).iosAppContext = {
343
+ appVersion: "1.0.0-ios",
344
+ }
345
+ ;(window as any).androidAppContext = {
346
+ appVersion: "1.0.0-android",
347
+ }
348
+
349
+ // When calling with IOS_MOBILE, should get iOS version
350
+ const iosVersion = await getShellVersion("IOS_MOBILE")
351
+ expect(iosVersion).toBe("1.0.0-ios")
352
+
353
+ // When calling with ANDROID_MOBILE, should still check iOS first (matching SDK behavior)
354
+ const androidVersion = await getShellVersion("ANDROID_MOBILE")
355
+ expect(androidVersion).toBe("1.0.0-ios")
356
+
357
+ delete (window as any).iosAppContext
228
358
  })
229
359
  })
230
360
 
@@ -250,10 +380,6 @@ describe("getShellVersion", () => {
250
380
 
251
381
  describe("Error Handling", () => {
252
382
  it("returns unknown and logs warning on error", async () => {
253
- const consoleWarnSpy = vi
254
- .spyOn(console, "warn")
255
- .mockImplementation(() => {})
256
-
257
383
  // Force an error by making tizen.application.getAppInfo throw
258
384
  ;(window as any).tizen = {
259
385
  application: {
@@ -266,12 +392,11 @@ describe("getShellVersion", () => {
266
392
  const version = await getShellVersion("SAMSUNG_TV")
267
393
 
268
394
  expect(version).toBe("unknown")
269
- expect(consoleWarnSpy).toHaveBeenCalledWith(
395
+ expect(logger.warn).toHaveBeenCalledWith(
270
396
  "[Shell] Failed to get shell version:",
271
397
  { error: expect.any(Error) }
272
398
  )
273
399
 
274
- consoleWarnSpy.mockRestore()
275
400
  delete (window as any).tizen
276
401
  })
277
402
  })