Skip to content

Commit f56a625

Browse files
committed
mods: the kit's helpers are mock.clock, mock.store and mock.env, MemoryClock is MockClock, and a rendered tree's text is the diff tests' own fixture; the declarations carry 'claude-code/testing' as generated from the kit's source
1 parent ead8806 commit f56a625

8 files changed

Lines changed: 496 additions & 231 deletions

File tree

‎mods/README.md‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ imports, the tier the mod loads in, and one `describe` titled with that name;
3636
what several tests share sits under `tests/fixtures/`, one export a file.
3737

3838
```ts
39-
import { describe, expect, memoryClock, test, tier } from 'claude-code/testing'
39+
import { describe, expect, mock, test, tier } from 'claude-code/testing'
4040

4141
tier('builtin')
4242

4343
describe('register', () => {
4444
test('outside a git repository /diff says so, opens nothing', async ($, on) => {
4545
const opened: string[] = []
46-
memoryClock(on)
46+
mock.clock(on)
4747
on('session.start', ($, e) => ({ cwd: e.cwd }))
4848
on('command.register', ($, e) => ({ value: { command: e.name } }))
4949
on('process.run', () => ({
@@ -67,13 +67,13 @@ describe('register', () => {
6767
})
6868
```
6969

70-
The kit's helpers are plain functions over `on`, each answering one noun
71-
beneath the mod where the test calls it: `memoryEnv(on, variables)` for
72-
`$.env`, `memoryStore(on, entries)` for `$.store`, and `memoryClock(on)` for
73-
`$.clock`, whose `advance(ms)` resolves every wait the mod asked for
74-
(`$.clock.sleep`, `after`, `every`) as the clock crosses it. `textOf(tree)`
75-
reads a rendered tree's text, and `$.ui.press({ plugin, key })` presses a
76-
`Button` the test rendered, as a click in the terminal does.
70+
The kit's `mock` answers the world beneath the mod from memory, noun by noun,
71+
each member a plain function over `on` registering hooks where the test calls
72+
it: `mock.env(on, variables)` for `$.env`, `mock.store(on, entries)` for
73+
`$.store`, and `mock.clock(on)` for `$.clock`, whose `advance(ms)` resolves
74+
every wait the mod asked for (`$.clock.sleep`, `after`, `every`) as the clock
75+
crosses it. `$.ui.press({ plugin, key })` presses a `Button` the test
76+
rendered, as a click in the terminal does.
7777

7878
`tsc -p mods/tsconfig.json` typechecks every mod's hooks and tests against
7979
`types/`.

‎mods/diff/tests/fixtures/in-repository.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Args, On } from 'claude-code'
2-
import { memoryStore } from 'claude-code/testing'
2+
import { mock } from 'claude-code/testing'
33

44
import { gitIn } from './git-in.js'
55
import { HINT_DRAWN } from './hint-drawn.js'
@@ -30,7 +30,7 @@ export function inRepository(on: On) {
3030
on('ui.invalidate', () => ({ value: undefined }))
3131
on('ui.render', { component: 'PromptHint' }, () => HINT_DRAWN)
3232
on('session.messages', () => ({ value: [] }))
33-
memoryStore(on)
33+
mock.store(on)
3434

3535
return { runs, opened: opened.kept, closed: closed.kept, clock }
3636
}

‎mods/diff/tests/fixtures/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ export * from './repository.js'
1313
export * from './session.js'
1414
export * from './settle-ms.js'
1515
export * from './starts-session.js'
16+
export * from './text-of.js'
1617

1718
export * as default from '.'
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { On } from 'claude-code'
2-
import { memoryClock } from 'claude-code/testing'
3-
import type { MemoryClock } from 'claude-code/testing'
2+
import { mock } from 'claude-code/testing'
3+
import type { MockClock } from 'claude-code/testing'
44

55
/**
66
* Answers what every /diff session asks first: its start, with its own
@@ -9,9 +9,9 @@ import type { MemoryClock } from 'claude-code/testing'
99
* @param on the test's `on`
1010
* @returns the clock the session reads, at 0 until the test moves it
1111
*/
12-
export function startsSession(on: On): MemoryClock {
12+
export function startsSession(on: On): MockClock {
1313
on('session.start', ($, e) => ({ cwd: e.cwd }))
1414
on('command.register', ($, e) => ({ value: { command: e.name } }))
1515

16-
return memoryClock(on)
16+
return mock.clock(on)
1717
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* A rendered tree's text as it reads: its strings and its labels, in order.
3+
*
4+
* @param tree what `$.ui.render` resolved to, or a part of it
5+
* @returns the text
6+
*/
7+
export function textOf(tree: unknown): string {
8+
if (typeof tree === 'string' || typeof tree === 'number') {
9+
return String(tree)
10+
}
11+
12+
if (Array.isArray(tree)) {
13+
return tree.map(textOf).join('')
14+
}
15+
16+
if (typeof tree !== 'object' || !tree) {
17+
return ''
18+
}
19+
20+
const props: unknown = Reflect.get(tree, 'props')
21+
const label: unknown =
22+
typeof props === 'object' && props ? Reflect.get(props, 'label') : undefined
23+
const lead = typeof label === 'string' ? label : ''
24+
25+
return `${lead}${textOf(Reflect.get(tree, 'children') ?? [])}`
26+
}

‎mods/diff/tests/register.test.ts‎

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
import {
2-
describe,
3-
expect,
4-
memoryClock,
5-
test,
6-
textOf,
7-
tier,
8-
} from 'claude-code/testing'
1+
import { describe, expect, mock, test, tier } from 'claude-code/testing'
92

103
import Limits from '../hooks/limits'
114
import Fixtures from './fixtures'
@@ -73,7 +66,7 @@ describe('register', () => {
7366

7467
test('when the built-in holds /diff, the mod stands down', async ($, on) => {
7568
const logged: string[] = []
76-
memoryClock(on)
69+
mock.clock(on)
7770
on('session.start', ($, e) => ({ cwd: e.cwd }))
7871
on('command.register', () => ({ deny: Fixtures.BUILTIN_HOLDS }))
7972
on('command.run', () => ({ text: 'the built-in /diff ran' }))
@@ -93,7 +86,7 @@ describe('register', () => {
9386

9487
test('a refusal the built-in did not cause is said aloud', async ($, on) => {
9588
const logged: string[] = []
96-
memoryClock(on)
89+
mock.clock(on)
9790
on('session.start', ($, e) => ({ cwd: e.cwd }))
9891
on('command.register', () => ({
9992
deny: '32 commands are registered already',
@@ -121,7 +114,7 @@ describe('register', () => {
121114
expect(world.opened.map(pane => pane.id)).toEqual(['diff'])
122115

123116
await world.clock.advance(Fixtures.SETTLE_MS)
124-
const drawn = textOf(await $.ui.render(Fixtures.PANE))
117+
const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
125118

126119
expect(drawn).toContain('1 file changed')
127120
expect(drawn).toContain('app.ts')

‎mods/telemetry/tests/register.test.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, expect, memoryEnv, test, tier } from 'claude-code/testing'
1+
import { describe, expect, mock, test, tier } from 'claude-code/testing'
22

33
import Fixtures from './fixtures'
44

@@ -9,7 +9,7 @@ describe('register', () => {
99
'a $.telemetry.log call from a plugin posts one first-party row',
1010
{ plugins: [Fixtures.recording] },
1111
async ($, on) => {
12-
memoryEnv(on, { USER_TYPE: 'ant' })
12+
mock.env(on, { USER_TYPE: 'ant' })
1313
const posts = Fixtures.firstPartySession(on)
1414

1515
expect(
@@ -39,7 +39,7 @@ describe('register', () => {
3939
'a row already named tengu_ is sent under its own name',
4040
{ plugins: [Fixtures.recording] },
4141
async ($, on) => {
42-
memoryEnv(on, {})
42+
mock.env(on, {})
4343
const posts = Fixtures.firstPartySession(on)
4444

4545
await $.command.run(
@@ -68,7 +68,7 @@ describe('register', () => {
6868
'nothing is sent for a person who asked not to be tracked',
6969
{ plugins: [Fixtures.recording] },
7070
async ($, on) => {
71-
memoryEnv(on, { DO_NOT_TRACK: '1' })
71+
mock.env(on, { DO_NOT_TRACK: '1' })
7272
const posts = Fixtures.firstPartySession(on)
7373
const { text } = await $.command.run(
7474
Fixtures.record(Fixtures.surveyAnswer()),
@@ -82,7 +82,7 @@ describe('register', () => {
8282
'nothing is sent on a third-party provider',
8383
{ plugins: [Fixtures.recording] },
8484
async ($, on) => {
85-
memoryEnv(on, { CLAUDE_CODE_USE_BEDROCK: '1' })
85+
mock.env(on, { CLAUDE_CODE_USE_BEDROCK: '1' })
8686
const posts = Fixtures.firstPartySession(on)
8787

8888
await $.command.run(Fixtures.record(Fixtures.surveyAnswer()))
@@ -95,7 +95,7 @@ describe('register', () => {
9595
'a session with no first-party credential is refused, nothing sent',
9696
{ plugins: [Fixtures.recording] },
9797
async ($, on) => {
98-
memoryEnv(on, {})
98+
mock.env(on, {})
9999
const posts = Fixtures.firstPartySession(on, null)
100100
const { text } = await $.command.run(
101101
Fixtures.record(Fixtures.surveyAnswer()),
@@ -113,7 +113,7 @@ describe('register', () => {
113113
'free text in a row is refused, nothing sent',
114114
{ plugins: [Fixtures.recording] },
115115
async ($, on) => {
116-
memoryEnv(on, {})
116+
mock.env(on, {})
117117
const posts = Fixtures.firstPartySession(on)
118118
const { text } = await $.command.run({
119119
...Fixtures.record(Fixtures.surveyAnswer()),

0 commit comments

Comments
 (0)