forked from OurHike/OurHike
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpush.test.ts
More file actions
121 lines (102 loc) · 4.66 KB
/
Copy pathpush.test.ts
File metadata and controls
121 lines (102 loc) · 4.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { describe, it, expect, vi, afterEach } from 'vitest'
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
import { join } from 'node:path'
// Imported explicitly rather than used as a global: tsconfig.app.json keeps
// node out of `types` on purpose, so browser code cannot reach for
// process.env and still typecheck. This test genuinely needs Node, so it asks
// for it by name instead of widening the app's config.
import { cwd } from 'node:process'
import { publishWrongWayAlert } from './push'
// TESTING.md invariant 17: "Serious warnings never enqueue a push; the
// wrong-way alert is the only push publisher in the client codebase."
//
// That is a CODEBASE-level claim, so it is checked at codebase level. A
// per-component test ("the warning sheet does not push") only proves the
// component someone thought to test; scanning the source proves the rule
// itself, including for a file nobody has written yet.
//
// This matters because the rule erodes one reasonable exception at a time.
// Every "but this one is genuinely urgent" is defensible alone, and the sum
// is an app that interrupts people on a mountain - which spends the trust
// budget the single alert was designed around (HIKER_SAFETY.md's own framing).
// Resolved from the working directory rather than import.meta.url, which
// vitest does not hand back as a file:// URL. Both candidates are checked so
// this works whether the suite is run from the repo root or from client/.
// The "actually has files in it" test below is what catches a bad path.
const SRC = [join(cwd(), 'src'), join(cwd(), 'client', 'src')].find((candidate) =>
existsSync(candidate),
) as string
/** Anything that would actually surface an OS-level notification. */
const NOTIFICATION_APIS = [
'new Notification(',
'showNotification(',
'requestPermission(',
'pushManager',
]
// The chokepoint itself, and this test. Nothing else.
const ALLOWED = ['lib\\push.ts', 'lib/push.ts', 'lib\\push.test.ts', 'lib/push.test.ts']
function sourceFiles(dir: string): string[] {
return readdirSync(dir).flatMap((entry: string) => {
const full = join(dir, entry)
if (statSync(full).isDirectory()) return sourceFiles(full)
return /\.(ts|tsx)$/.test(entry) ? [full] : []
})
}
describe('the one-notification policy, as a codebase invariant', () => {
it('has exactly one module that touches a notification API', () => {
const offenders = sourceFiles(SRC)
.filter((file) => !ALLOWED.some((allowed) => file.endsWith(allowed)))
.filter((file) => {
const text = readFileSync(file, 'utf8')
return NOTIFICATION_APIS.some((api) => text.includes(api))
})
.map((file) => file.slice(SRC.length))
expect(offenders).toEqual([])
})
it('scans a source tree that actually has files in it', () => {
// Guards the guard: a broken path would make the test above pass
// vacuously forever.
expect(sourceFiles(SRC).length).toBeGreaterThan(20)
})
it('would notice a violation if one were introduced', () => {
// Proves the matcher works, rather than trusting that it does.
const pretendModule = "export function ping() { new Notification('hi') }"
expect(NOTIFICATION_APIS.some((api) => pretendModule.includes(api))).toBe(true)
})
})
describe('publishWrongWayAlert', () => {
afterEach(() => {
vi.unstubAllGlobals()
})
it('sends nothing when permission has not been granted', async () => {
const spy = vi.fn()
vi.stubGlobal(
'Notification',
Object.assign(spy, { permission: 'default' as NotificationPermission }),
)
expect(await publishWrongWayAlert({ title: 'Off trail', body: '…' })).toBe(false)
expect(spy).not.toHaveBeenCalled()
})
it('sends nothing when the platform has no Notification API at all', async () => {
vi.stubGlobal('Notification', undefined)
expect(await publishWrongWayAlert({ title: 'Off trail', body: '…' })).toBe(false)
})
it('sends the alert once permission is granted', async () => {
const spy = vi.fn()
vi.stubGlobal(
'Notification',
Object.assign(spy, { permission: 'granted' as NotificationPermission }),
)
expect(await publishWrongWayAlert({ title: 'Off trail', body: 'Turn around' })).toBe(
true,
)
expect(spy).toHaveBeenCalledWith('Off trail', { body: 'Turn around' })
})
it('never asks for permission itself - that belongs to the hike-start flow', async () => {
// HIKER_SAFETY.md puts the prompt at hike start, where the reason is
// concrete. Asking here would mean prompting at the exact moment someone
// is already lost.
const push = readFileSync(join(SRC, 'lib', 'push.ts'), 'utf8')
expect(push).not.toContain('requestPermission')
})
})