also looking at this
fix(ios): hand a notification tap that launches the app to the page that asks for it
#255
5 files
+194
-9
| @@ -91,6 +91,9 @@ function loadPage(beforeInject?: (page: Record<string, any>) => void, seed = SEE | ||
| 91 | 91 | // What DeepLinkManager.dispatchDeepLink evaluates, once the script has run. |
| 92 | 92 | link: (url: string, initial: boolean) => |
| 93 | 93 | page.dispatchEvent(new CustomEvent('craftDeepLink', { detail: { url, initial } })), |
| 94 | // What CraftEventManager.handleNotificationResponse evaluates. | |
| 95 | tap: (detail: unknown) => | |
| 96 | page.dispatchEvent(new CustomEvent('craftNotificationResponse', { detail })), | |
| 94 | 97 | } |
| 95 | 98 | } |
| 96 | 99 | |
| @@ -201,6 +204,55 @@ describe('the injected iOS page script', () => { | ||
| 201 | 204 | }) |
| 202 | 205 | } |
| 203 | 206 | |
| 207 | // A notification tap on a cold launch is flushed the moment the bridge is | |
| 208 | // ready, and a page that wires its listener after its router hydrates was | |
| 209 | // not listening yet: "Try it" opened the home screen instead. Held the way | |
| 210 | // deep links are (#198), and handed to the first subscriber. | |
| 211 | it('hands a tap that arrived before anyone subscribed to the first subscriber', async () => { | |
| 212 | const page = loadPage() | |
| 213 | page.tap({ screen: 'plant-id' }) | |
| 214 | ||
| 215 | const seen: unknown[] = [] | |
| 216 | page.craft.notifications.onTap((detail: unknown) => seen.push(detail)) | |
| 217 | await tick() | |
| 218 | ||
| 219 | expect(seen).toEqual([{ screen: 'plant-id' }]) | |
| 220 | }) | |
| 221 | ||
| 222 | it('keeps onTap on the notifications object the page ends up with', () => { | |
| 223 | // installCraftMobileContract replaces craft.notifications after the | |
| 224 | // replay attaches onTap, and onTap survives only because it copies own | |
| 225 | // properties across. Reorder those two blocks and it would vanish with | |
| 226 | // nothing else failing, so this reads the final object. | |
| 227 | const page = loadPage() | |
| 228 | expect(typeof page.craft.notifications.onTap).toBe('function') | |
| 229 | expect(typeof page.craft.notifications.onTap(() => {})).toBe('function') | |
| 230 | // And the contract's own additions are still there beside it. | |
| 231 | expect(typeof page.craft.notifications.show).toBe('function') | |
| 232 | }) | |
| 233 | ||
| 234 | it('delivers a tap once, live, to a page already subscribed', async () => { | |
| 235 | const page = loadPage() | |
| 236 | const seen: unknown[] = [] | |
| 237 | page.craft.notifications.onTap((detail: unknown) => seen.push(detail)) | |
| 238 | await tick() | |
| 239 | ||
| 240 | page.tap({ screen: 'recap' }) | |
| 241 | expect(seen).toEqual([{ screen: 'recap' }]) | |
| 242 | }) | |
| 243 | ||
| 244 | it('drops held taps for a subscriber that unsubscribed in the same tick', async () => { | |
| 245 | const page = loadPage() | |
| 246 | page.tap({ screen: 'plant-id' }) | |
| 247 | ||
| 248 | const seen: unknown[] = [] | |
| 249 | const unsubscribe = page.craft.notifications.onTap((detail: unknown) => seen.push(detail)) | |
| 250 | unsubscribe() | |
| 251 | await tick() | |
| 252 | ||
| 253 | expect(seen).toEqual([]) | |
| 254 | }) | |
| 255 | ||
| 204 | 256 | // #198 and #215. The Android page script runs the same block; its own test |
| 205 | 257 | // covers the rest of the contract. |
| 206 | 258 | const tick = () => new Promise(resolve => setTimeout(resolve, 5)) |