@flighthq/host-web 0.4.0-edge.1908.751021c

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 (165) hide show
  1. package/dist/contract.d.ts +32 -0
  2. package/dist/contract.d.ts.map +1 -0
  3. package/dist/contract.js +32 -0
  4. package/dist/contract.js.map +1 -0
  5. package/dist/enableHostWeb.d.ts +2 -0
  6. package/dist/enableHostWeb.d.ts.map +1 -0
  7. package/dist/enableHostWeb.js +47 -0
  8. package/dist/enableHostWeb.js.map +1 -0
  9. package/dist/index.d.ts +32 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +32 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/webAccessibility.d.ts +3 -0
  14. package/dist/webAccessibility.d.ts.map +1 -0
  15. package/dist/webAccessibility.js +12 -0
  16. package/dist/webAccessibility.js.map +1 -0
  17. package/dist/webApp.d.ts +3 -0
  18. package/dist/webApp.d.ts.map +1 -0
  19. package/dist/webApp.js +156 -0
  20. package/dist/webApp.js.map +1 -0
  21. package/dist/webClipboard.d.ts +3 -0
  22. package/dist/webClipboard.d.ts.map +1 -0
  23. package/dist/webClipboard.js +132 -0
  24. package/dist/webClipboard.js.map +1 -0
  25. package/dist/webConnectivity.d.ts +3 -0
  26. package/dist/webConnectivity.d.ts.map +1 -0
  27. package/dist/webConnectivity.js +58 -0
  28. package/dist/webConnectivity.js.map +1 -0
  29. package/dist/webCursor.d.ts +3 -0
  30. package/dist/webCursor.d.ts.map +1 -0
  31. package/dist/webCursor.js +8 -0
  32. package/dist/webCursor.js.map +1 -0
  33. package/dist/webDevice.d.ts +3 -0
  34. package/dist/webDevice.d.ts.map +1 -0
  35. package/dist/webDevice.js +12 -0
  36. package/dist/webDevice.js.map +1 -0
  37. package/dist/webDialog.d.ts +3 -0
  38. package/dist/webDialog.d.ts.map +1 -0
  39. package/dist/webDialog.js +47 -0
  40. package/dist/webDialog.js.map +1 -0
  41. package/dist/webFilesystem.d.ts +3 -0
  42. package/dist/webFilesystem.d.ts.map +1 -0
  43. package/dist/webFilesystem.js +12 -0
  44. package/dist/webFilesystem.js.map +1 -0
  45. package/dist/webGeolocation.d.ts +3 -0
  46. package/dist/webGeolocation.d.ts.map +1 -0
  47. package/dist/webGeolocation.js +58 -0
  48. package/dist/webGeolocation.js.map +1 -0
  49. package/dist/webGlyphRasterizer.d.ts +5 -0
  50. package/dist/webGlyphRasterizer.d.ts.map +1 -0
  51. package/dist/webGlyphRasterizer.js +101 -0
  52. package/dist/webGlyphRasterizer.js.map +1 -0
  53. package/dist/webHaptics.d.ts +3 -0
  54. package/dist/webHaptics.d.ts.map +1 -0
  55. package/dist/webHaptics.js +12 -0
  56. package/dist/webHaptics.js.map +1 -0
  57. package/dist/webImage.d.ts +3 -0
  58. package/dist/webImage.d.ts.map +1 -0
  59. package/dist/webImage.js +26 -0
  60. package/dist/webImage.js.map +1 -0
  61. package/dist/webKeyboard.d.ts +3 -0
  62. package/dist/webKeyboard.d.ts.map +1 -0
  63. package/dist/webKeyboard.js +12 -0
  64. package/dist/webKeyboard.js.map +1 -0
  65. package/dist/webLifecycle.d.ts +3 -0
  66. package/dist/webLifecycle.d.ts.map +1 -0
  67. package/dist/webLifecycle.js +12 -0
  68. package/dist/webLifecycle.js.map +1 -0
  69. package/dist/webLoop.d.ts +3 -0
  70. package/dist/webLoop.d.ts.map +1 -0
  71. package/dist/webLoop.js +38 -0
  72. package/dist/webLoop.js.map +1 -0
  73. package/dist/webMediasession.d.ts +3 -0
  74. package/dist/webMediasession.d.ts.map +1 -0
  75. package/dist/webMediasession.js +51 -0
  76. package/dist/webMediasession.js.map +1 -0
  77. package/dist/webMenu.d.ts +3 -0
  78. package/dist/webMenu.d.ts.map +1 -0
  79. package/dist/webMenu.js +31 -0
  80. package/dist/webMenu.js.map +1 -0
  81. package/dist/webNotification.d.ts +3 -0
  82. package/dist/webNotification.d.ts.map +1 -0
  83. package/dist/webNotification.js +85 -0
  84. package/dist/webNotification.js.map +1 -0
  85. package/dist/webPermissions.d.ts +3 -0
  86. package/dist/webPermissions.d.ts.map +1 -0
  87. package/dist/webPermissions.js +37 -0
  88. package/dist/webPermissions.js.map +1 -0
  89. package/dist/webPlatform.d.ts +3 -0
  90. package/dist/webPlatform.d.ts.map +1 -0
  91. package/dist/webPlatform.js +12 -0
  92. package/dist/webPlatform.js.map +1 -0
  93. package/dist/webPower.d.ts +3 -0
  94. package/dist/webPower.d.ts.map +1 -0
  95. package/dist/webPower.js +217 -0
  96. package/dist/webPower.js.map +1 -0
  97. package/dist/webProtocol.d.ts +3 -0
  98. package/dist/webProtocol.d.ts.map +1 -0
  99. package/dist/webProtocol.js +78 -0
  100. package/dist/webProtocol.js.map +1 -0
  101. package/dist/webScreen.d.ts +3 -0
  102. package/dist/webScreen.d.ts.map +1 -0
  103. package/dist/webScreen.js +12 -0
  104. package/dist/webScreen.js.map +1 -0
  105. package/dist/webSensors.d.ts +3 -0
  106. package/dist/webSensors.d.ts.map +1 -0
  107. package/dist/webSensors.js +12 -0
  108. package/dist/webSensors.js.map +1 -0
  109. package/dist/webShare.d.ts +3 -0
  110. package/dist/webShare.d.ts.map +1 -0
  111. package/dist/webShare.js +33 -0
  112. package/dist/webShare.js.map +1 -0
  113. package/dist/webShell.d.ts +3 -0
  114. package/dist/webShell.d.ts.map +1 -0
  115. package/dist/webShell.js +51 -0
  116. package/dist/webShell.js.map +1 -0
  117. package/dist/webStatusbar.d.ts +3 -0
  118. package/dist/webStatusbar.d.ts.map +1 -0
  119. package/dist/webStatusbar.js +52 -0
  120. package/dist/webStatusbar.js.map +1 -0
  121. package/dist/webStorage.d.ts +3 -0
  122. package/dist/webStorage.d.ts.map +1 -0
  123. package/dist/webStorage.js +12 -0
  124. package/dist/webStorage.js.map +1 -0
  125. package/dist/webWebcam.d.ts +3 -0
  126. package/dist/webWebcam.d.ts.map +1 -0
  127. package/dist/webWebcam.js +12 -0
  128. package/dist/webWebcam.js.map +1 -0
  129. package/dist/webWindow.d.ts +3 -0
  130. package/dist/webWindow.d.ts.map +1 -0
  131. package/dist/webWindow.js +135 -0
  132. package/dist/webWindow.js.map +1 -0
  133. package/package.json +76 -0
  134. package/src/enableHostWeb.test.ts +12 -0
  135. package/src/evidence.test.ts +172 -0
  136. package/src/webAccessibility.test.ts +22 -0
  137. package/src/webApp.test.ts +22 -0
  138. package/src/webClipboard.test.ts +22 -0
  139. package/src/webConnectivity.test.ts +22 -0
  140. package/src/webCursor.test.ts +26 -0
  141. package/src/webDevice.test.ts +22 -0
  142. package/src/webDialog.test.ts +22 -0
  143. package/src/webFilesystem.test.ts +22 -0
  144. package/src/webGeolocation.test.ts +22 -0
  145. package/src/webGlyphRasterizer.test.ts +153 -0
  146. package/src/webHaptics.test.ts +22 -0
  147. package/src/webImage.test.ts +22 -0
  148. package/src/webKeyboard.test.ts +22 -0
  149. package/src/webLifecycle.test.ts +22 -0
  150. package/src/webLoop.test.ts +123 -0
  151. package/src/webMediasession.test.ts +22 -0
  152. package/src/webMenu.test.ts +22 -0
  153. package/src/webNotification.test.ts +22 -0
  154. package/src/webPermissions.test.ts +22 -0
  155. package/src/webPlatform.test.ts +22 -0
  156. package/src/webPower.test.ts +22 -0
  157. package/src/webProtocol.test.ts +22 -0
  158. package/src/webScreen.test.ts +22 -0
  159. package/src/webSensors.test.ts +22 -0
  160. package/src/webShare.test.ts +22 -0
  161. package/src/webShell.test.ts +22 -0
  162. package/src/webStatusbar.test.ts +22 -0
  163. package/src/webStorage.test.ts +22 -0
  164. package/src/webWebcam.test.ts +22 -0
  165. package/src/webWindow.test.ts +22 -0
@@ -0,0 +1,22 @@
1
+ import { enableHostWebConnectivity, resetHostWebConnectivityForTest } from './webConnectivity';
2
+
3
+ describe('enableHostWebConnectivity', () => {
4
+ afterEach(() => resetHostWebConnectivityForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebConnectivity()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebConnectivity();
12
+ expect(() => enableHostWebConnectivity()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebConnectivityForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebConnectivity();
19
+ resetHostWebConnectivityForTest();
20
+ expect(() => enableHostWebConnectivity()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { createWebCursorBackend } from './webCursor';
4
+
5
+ describe('createWebCursorBackend', () => {
6
+ it('sets element.style.cursor to the given cursor value', () => {
7
+ const element = document.createElement('div');
8
+ const backend = createWebCursorBackend(element);
9
+ backend.setCursor('pointer');
10
+ expect(element.style.cursor).toBe('pointer');
11
+ });
12
+
13
+ it('clears element.style.cursor when given null', () => {
14
+ const element = document.createElement('div');
15
+ const backend = createWebCursorBackend(element);
16
+ backend.setCursor('pointer');
17
+ backend.setCursor(null);
18
+ expect(element.style.cursor).toBe('');
19
+ });
20
+
21
+ it('constructs without a DOM side effect', () => {
22
+ const element = document.createElement('div');
23
+ createWebCursorBackend(element);
24
+ expect(element.style.cursor).toBe('');
25
+ });
26
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebDevice, resetHostWebDeviceForTest } from './webDevice';
2
+
3
+ describe('enableHostWebDevice', () => {
4
+ afterEach(() => resetHostWebDeviceForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebDevice()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebDevice();
12
+ expect(() => enableHostWebDevice()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebDeviceForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebDevice();
19
+ resetHostWebDeviceForTest();
20
+ expect(() => enableHostWebDevice()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebDialog, resetHostWebDialogForTest } from './webDialog';
2
+
3
+ describe('enableHostWebDialog', () => {
4
+ afterEach(() => resetHostWebDialogForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebDialog()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebDialog();
12
+ expect(() => enableHostWebDialog()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebDialogForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebDialog();
19
+ resetHostWebDialogForTest();
20
+ expect(() => enableHostWebDialog()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebFileSystem, resetHostWebFilesystemForTest } from './webFilesystem';
2
+
3
+ describe('enableHostWebFileSystem', () => {
4
+ afterEach(() => resetHostWebFilesystemForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebFileSystem()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebFileSystem();
12
+ expect(() => enableHostWebFileSystem()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebFilesystemForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebFileSystem();
19
+ resetHostWebFilesystemForTest();
20
+ expect(() => enableHostWebFileSystem()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebGeolocation, resetHostWebGeolocationForTest } from './webGeolocation';
2
+
3
+ describe('enableHostWebGeolocation', () => {
4
+ afterEach(() => resetHostWebGeolocationForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebGeolocation()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebGeolocation();
12
+ expect(() => enableHostWebGeolocation()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebGeolocationForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebGeolocation();
19
+ resetHostWebGeolocationForTest();
20
+ expect(() => enableHostWebGeolocation()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,153 @@
1
+ import {
2
+ explainGlyphRasterizerBackend,
3
+ getGlyphRasterizerBackend,
4
+ installGlyphRasterizerHostBackend,
5
+ resetGlyphRasterizerBackendForTest,
6
+ } from '@flighthq/glyphatlas/contract';
7
+ import { afterEach, describe, expect, it, vi } from 'vitest';
8
+
9
+ import {
10
+ createWebGlyphRasterizerBackend,
11
+ enableHostWebGlyphRasterizer,
12
+ resetHostWebGlyphRasterizerForTest,
13
+ } from './webGlyphRasterizer';
14
+
15
+ describe('createWebGlyphRasterizerBackend', () => {
16
+ afterEach(resetGlyphRasterizerBackendForTest);
17
+
18
+ it('constructs a backend with rasterize and measureMetrics', () => {
19
+ const backend = createWebGlyphRasterizerBackend();
20
+ expect(backend.rasterize).toBeTypeOf('function');
21
+ expect(backend.measureMetrics).toBeTypeOf('function');
22
+ });
23
+
24
+ it('rasterize returns null and observes failure when no canvas context is available', () => {
25
+ const backend = createWebGlyphRasterizerBackend();
26
+ installGlyphRasterizerHostBackend(backend);
27
+ const saved = HTMLCanvasElement.prototype.getContext;
28
+ HTMLCanvasElement.prototype.getContext = (() => null) as typeof HTMLCanvasElement.prototype.getContext;
29
+ try {
30
+ expect(backend.rasterize(65, { fontFamily: 'x', fontSize: 16 })).toBeNull();
31
+ expect(explainGlyphRasterizerBackend().viability).toBe('runtime-api-unavailable');
32
+ expect(explainGlyphRasterizerBackend().operation).toBe('rasterize');
33
+ } finally {
34
+ HTMLCanvasElement.prototype.getContext = saved;
35
+ }
36
+ });
37
+ });
38
+
39
+ describe('enableHostWebGlyphRasterizer', () => {
40
+ afterEach(() => {
41
+ resetGlyphRasterizerBackendForTest();
42
+ resetHostWebGlyphRasterizerForTest();
43
+ vi.restoreAllMocks();
44
+ });
45
+
46
+ it('installs a host backend so get returns a non-sentinel', () => {
47
+ enableHostWebGlyphRasterizer();
48
+ const backend = getGlyphRasterizerBackend();
49
+ expect(backend).not.toBe(null);
50
+ expect(explainGlyphRasterizerBackend().layer).toBe('host');
51
+ });
52
+
53
+ it('is idempotent — second call preserves provider identity and allocates nothing', () => {
54
+ enableHostWebGlyphRasterizer();
55
+ const first = getGlyphRasterizerBackend();
56
+ enableHostWebGlyphRasterizer();
57
+ const second = getGlyphRasterizerBackend();
58
+ expect(first).toBe(second);
59
+ });
60
+
61
+ it('starts unobserved — no viability claimed until a real call', () => {
62
+ enableHostWebGlyphRasterizer();
63
+ expect(explainGlyphRasterizerBackend()).toEqual({
64
+ conflict: false,
65
+ layer: 'host',
66
+ operation: null,
67
+ viability: 'unobserved',
68
+ });
69
+ });
70
+
71
+ it('observes available after a successful rasterize call', () => {
72
+ enableHostWebGlyphRasterizer();
73
+ const backend = getGlyphRasterizerBackend();
74
+ const result = backend.rasterize(65, { fontFamily: 'sans-serif', fontSize: 16 });
75
+ if (result !== null) {
76
+ expect(explainGlyphRasterizerBackend()).toEqual({
77
+ conflict: false,
78
+ layer: 'host',
79
+ operation: 'rasterize',
80
+ viability: 'available',
81
+ });
82
+ }
83
+ });
84
+
85
+ it('observes runtime-api-unavailable when context returns null', () => {
86
+ const saved = HTMLCanvasElement.prototype.getContext;
87
+ HTMLCanvasElement.prototype.getContext = (() => null) as typeof HTMLCanvasElement.prototype.getContext;
88
+ try {
89
+ enableHostWebGlyphRasterizer();
90
+ const backend = getGlyphRasterizerBackend();
91
+ backend.rasterize(65, { fontFamily: 'x', fontSize: 16 });
92
+ } finally {
93
+ HTMLCanvasElement.prototype.getContext = saved;
94
+ }
95
+ expect(explainGlyphRasterizerBackend()).toEqual({
96
+ conflict: false,
97
+ layer: 'host',
98
+ operation: 'rasterize',
99
+ viability: 'runtime-api-unavailable',
100
+ });
101
+ });
102
+
103
+ it('reflects recovery: broken environment then restored', () => {
104
+ enableHostWebGlyphRasterizer();
105
+ const backend = getGlyphRasterizerBackend();
106
+
107
+ // Break the environment — rasterize reports failure
108
+ const saved = HTMLCanvasElement.prototype.getContext;
109
+ HTMLCanvasElement.prototype.getContext = (() => null) as typeof HTMLCanvasElement.prototype.getContext;
110
+ backend.rasterize(65, { fontFamily: 'x', fontSize: 16 });
111
+ expect(explainGlyphRasterizerBackend().viability).toBe('runtime-api-unavailable');
112
+
113
+ // Restore — next call reports success
114
+ HTMLCanvasElement.prototype.getContext = saved;
115
+ const result = backend.rasterize(65, { fontFamily: 'sans-serif', fontSize: 16 });
116
+ if (result !== null) {
117
+ expect(explainGlyphRasterizerBackend().viability).toBe('available');
118
+ }
119
+ });
120
+
121
+ it('does not conflict with a prior install of the same backend via direct install', () => {
122
+ enableHostWebGlyphRasterizer();
123
+ const backend = getGlyphRasterizerBackend();
124
+ installGlyphRasterizerHostBackend(backend);
125
+ expect(explainGlyphRasterizerBackend().conflict).toBe(false);
126
+ });
127
+
128
+ it('observes available even when rasterize returns null for a zero-ink glyph', () => {
129
+ enableHostWebGlyphRasterizer();
130
+ const backend = getGlyphRasterizerBackend();
131
+ // Space (codepoint 32) has no visible pixels — rasterize returns null, but the API is working
132
+ const result = backend.rasterize(32, { fontFamily: 'sans-serif', fontSize: 16 });
133
+ expect(result).toBeNull();
134
+ expect(explainGlyphRasterizerBackend()).toEqual({
135
+ conflict: false,
136
+ layer: 'host',
137
+ operation: 'rasterize',
138
+ viability: 'available',
139
+ });
140
+ });
141
+ });
142
+
143
+ describe('resetHostWebGlyphRasterizerForTest', () => {
144
+ afterEach(resetGlyphRasterizerBackendForTest);
145
+
146
+ it('clears the enabler flag so a subsequent enable re-installs', () => {
147
+ enableHostWebGlyphRasterizer();
148
+ resetHostWebGlyphRasterizerForTest();
149
+ resetGlyphRasterizerBackendForTest();
150
+ enableHostWebGlyphRasterizer();
151
+ expect(explainGlyphRasterizerBackend().layer).toBe('host');
152
+ });
153
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebHaptics, resetHostWebHapticsForTest } from './webHaptics';
2
+
3
+ describe('enableHostWebHaptics', () => {
4
+ afterEach(() => resetHostWebHapticsForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebHaptics()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebHaptics();
12
+ expect(() => enableHostWebHaptics()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebHapticsForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebHaptics();
19
+ resetHostWebHapticsForTest();
20
+ expect(() => enableHostWebHaptics()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebImage, resetHostWebImageForTest } from './webImage';
2
+
3
+ describe('enableHostWebImage', () => {
4
+ afterEach(() => resetHostWebImageForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebImage()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebImage();
12
+ expect(() => enableHostWebImage()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebImageForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebImage();
19
+ resetHostWebImageForTest();
20
+ expect(() => enableHostWebImage()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebSoftKeyboard, resetHostWebKeyboardForTest } from './webKeyboard';
2
+
3
+ describe('enableHostWebSoftKeyboard', () => {
4
+ afterEach(() => resetHostWebKeyboardForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebSoftKeyboard()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebSoftKeyboard();
12
+ expect(() => enableHostWebSoftKeyboard()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebKeyboardForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebSoftKeyboard();
19
+ resetHostWebKeyboardForTest();
20
+ expect(() => enableHostWebSoftKeyboard()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebLifecycle, resetHostWebLifecycleForTest } from './webLifecycle';
2
+
3
+ describe('enableHostWebLifecycle', () => {
4
+ afterEach(() => resetHostWebLifecycleForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebLifecycle()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebLifecycle();
12
+ expect(() => enableHostWebLifecycle()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebLifecycleForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebLifecycle();
19
+ resetHostWebLifecycleForTest();
20
+ expect(() => enableHostWebLifecycle()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,123 @@
1
+ import { explainLoopBackend, getLoopBackend, resetLoopBackendForTest } from '@flighthq/application/contract';
2
+ import { afterEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { enableHostWebLoop, resetHostWebLoopForTest } from './webLoop';
5
+
6
+ describe('enableHostWebLoop', () => {
7
+ afterEach(() => {
8
+ resetLoopBackendForTest();
9
+ resetHostWebLoopForTest();
10
+ vi.restoreAllMocks();
11
+ });
12
+
13
+ it('installs a host backend so get returns non-null', () => {
14
+ enableHostWebLoop();
15
+ const backend = getLoopBackend();
16
+ expect(backend).not.toBeNull();
17
+ expect(backend!.now()).toBeTypeOf('number');
18
+ expect(explainLoopBackend().layer).toBe('host');
19
+ });
20
+
21
+ it('is idempotent — second call preserves provider identity', () => {
22
+ enableHostWebLoop();
23
+ const first = getLoopBackend();
24
+ enableHostWebLoop();
25
+ const second = getLoopBackend();
26
+ expect(first).toBe(second);
27
+ });
28
+
29
+ it('starts unobserved — no viability claimed until a real call', () => {
30
+ enableHostWebLoop();
31
+ expect(explainLoopBackend()).toEqual({
32
+ conflict: false,
33
+ layer: 'host',
34
+ operation: null,
35
+ viability: 'unobserved',
36
+ });
37
+ });
38
+
39
+ it('observes available after requestFrame call', () => {
40
+ enableHostWebLoop();
41
+ const backend = getLoopBackend()!;
42
+ const handle = backend.requestFrame(() => {});
43
+ expect(handle).toBeTypeOf('number');
44
+ expect(explainLoopBackend()).toEqual({
45
+ conflict: false,
46
+ layer: 'host',
47
+ operation: 'requestFrame',
48
+ viability: 'available',
49
+ });
50
+ });
51
+
52
+ it('every requestFrame re-observes — not one-shot', () => {
53
+ enableHostWebLoop();
54
+ const backend = getLoopBackend()!;
55
+ backend.requestFrame(() => {});
56
+ backend.requestFrame(() => {});
57
+ expect(explainLoopBackend().operation).toBe('requestFrame');
58
+ expect(explainLoopBackend().viability).toBe('available');
59
+ });
60
+
61
+ it('cancelFrame observes independently from requestFrame', () => {
62
+ enableHostWebLoop();
63
+ const backend = getLoopBackend()!;
64
+ const handle = backend.requestFrame(() => {});
65
+ expect(explainLoopBackend().operation).toBe('requestFrame');
66
+ backend.cancelFrame(handle);
67
+ expect(explainLoopBackend().operation).toBe('cancelFrame');
68
+ expect(explainLoopBackend().viability).toBe('available');
69
+ });
70
+
71
+ it('loss then recovery — requestFrame failure reverts to available on success', () => {
72
+ enableHostWebLoop();
73
+ const backend = getLoopBackend()!;
74
+ backend.requestFrame(() => {});
75
+ expect(explainLoopBackend().viability).toBe('available');
76
+
77
+ const raf = vi.fn().mockImplementation(() => {
78
+ throw new Error('rAF unavailable');
79
+ });
80
+ vi.stubGlobal('requestAnimationFrame', raf);
81
+ backend.requestFrame(() => {});
82
+ expect(explainLoopBackend().viability).toBe('runtime-api-unavailable');
83
+ expect(explainLoopBackend().operation).toBe('requestFrame');
84
+
85
+ vi.stubGlobal('requestAnimationFrame', vi.fn().mockReturnValue(99));
86
+ backend.requestFrame(() => {});
87
+ expect(explainLoopBackend().viability).toBe('available');
88
+ });
89
+
90
+ it('cancelFrame failure observes unavailable', () => {
91
+ enableHostWebLoop();
92
+ const backend = getLoopBackend()!;
93
+ backend.requestFrame(() => {});
94
+
95
+ vi.stubGlobal(
96
+ 'cancelAnimationFrame',
97
+ vi.fn().mockImplementation(() => {
98
+ throw new Error('cAF unavailable');
99
+ }),
100
+ );
101
+ backend.cancelFrame(1);
102
+ expect(explainLoopBackend().operation).toBe('cancelFrame');
103
+ expect(explainLoopBackend().viability).toBe('runtime-api-unavailable');
104
+ });
105
+
106
+ it('now returns a positive number', () => {
107
+ enableHostWebLoop();
108
+ const backend = getLoopBackend()!;
109
+ expect(backend.now()).toBeGreaterThanOrEqual(0);
110
+ });
111
+ });
112
+
113
+ describe('resetHostWebLoopForTest', () => {
114
+ afterEach(resetLoopBackendForTest);
115
+
116
+ it('clears the enabler flag so a subsequent enable re-installs', () => {
117
+ enableHostWebLoop();
118
+ resetHostWebLoopForTest();
119
+ resetLoopBackendForTest();
120
+ enableHostWebLoop();
121
+ expect(explainLoopBackend().layer).toBe('host');
122
+ });
123
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebMediaSession, resetHostWebMediasessionForTest } from './webMediasession';
2
+
3
+ describe('enableHostWebMediaSession', () => {
4
+ afterEach(() => resetHostWebMediasessionForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebMediaSession()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebMediaSession();
12
+ expect(() => enableHostWebMediaSession()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebMediasessionForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebMediaSession();
19
+ resetHostWebMediasessionForTest();
20
+ expect(() => enableHostWebMediaSession()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebMenu, resetHostWebMenuForTest } from './webMenu';
2
+
3
+ describe('enableHostWebMenu', () => {
4
+ afterEach(() => resetHostWebMenuForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebMenu()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebMenu();
12
+ expect(() => enableHostWebMenu()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebMenuForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebMenu();
19
+ resetHostWebMenuForTest();
20
+ expect(() => enableHostWebMenu()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebNotification, resetHostWebNotificationForTest } from './webNotification';
2
+
3
+ describe('enableHostWebNotification', () => {
4
+ afterEach(() => resetHostWebNotificationForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebNotification()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebNotification();
12
+ expect(() => enableHostWebNotification()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebNotificationForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebNotification();
19
+ resetHostWebNotificationForTest();
20
+ expect(() => enableHostWebNotification()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebPermission, resetHostWebPermissionsForTest } from './webPermissions';
2
+
3
+ describe('enableHostWebPermission', () => {
4
+ afterEach(() => resetHostWebPermissionsForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebPermission()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebPermission();
12
+ expect(() => enableHostWebPermission()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebPermissionsForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebPermission();
19
+ resetHostWebPermissionsForTest();
20
+ expect(() => enableHostWebPermission()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebPlatform, resetHostWebPlatformForTest } from './webPlatform';
2
+
3
+ describe('enableHostWebPlatform', () => {
4
+ afterEach(() => resetHostWebPlatformForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebPlatform()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebPlatform();
12
+ expect(() => enableHostWebPlatform()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebPlatformForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebPlatform();
19
+ resetHostWebPlatformForTest();
20
+ expect(() => enableHostWebPlatform()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebPower, resetHostWebPowerForTest } from './webPower';
2
+
3
+ describe('enableHostWebPower', () => {
4
+ afterEach(() => resetHostWebPowerForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebPower()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebPower();
12
+ expect(() => enableHostWebPower()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebPowerForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebPower();
19
+ resetHostWebPowerForTest();
20
+ expect(() => enableHostWebPower()).not.toThrow();
21
+ });
22
+ });
@@ -0,0 +1,22 @@
1
+ import { enableHostWebProtocol, resetHostWebProtocolForTest } from './webProtocol';
2
+
3
+ describe('enableHostWebProtocol', () => {
4
+ afterEach(() => resetHostWebProtocolForTest());
5
+
6
+ it('does not throw on first call', () => {
7
+ expect(() => enableHostWebProtocol()).not.toThrow();
8
+ });
9
+
10
+ it('is idempotent', () => {
11
+ enableHostWebProtocol();
12
+ expect(() => enableHostWebProtocol()).not.toThrow();
13
+ });
14
+ });
15
+
16
+ describe('resetHostWebProtocolForTest', () => {
17
+ it('allows re-enabling after reset', () => {
18
+ enableHostWebProtocol();
19
+ resetHostWebProtocolForTest();
20
+ expect(() => enableHostWebProtocol()).not.toThrow();
21
+ });
22
+ });