Skip to content

Commit fc760cc

Browse files
isaacroldanClaude Code
andcommitted
Add tests for devSessionWatchConfig and watchedFiles behavior
Co-authored-by: Claude Code <claude-code@anthropic.com>
1 parent 5dfacdb commit fc760cc

File tree

6 files changed

+93
-13
lines changed

6 files changed

+93
-13
lines changed

packages/app/src/cli/models/app/loader.test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,12 +2070,9 @@ describe('load', () => {
20702070
const app = await loadTestingApp()
20712071

20722072
// Then
2073-
expect(app.allExtensions).toHaveLength(7)
2073+
expect(app.allExtensions).toHaveLength(6)
20742074
const extensionsConfig = app.allExtensions.map((ext) => ext.configuration)
20752075
expect(extensionsConfig).toEqual([
2076-
expect.objectContaining({
2077-
name: 'for-testing',
2078-
}),
20792076
expect.objectContaining({
20802077
name: 'for-testing',
20812078
}),
@@ -2131,12 +2128,9 @@ describe('load', () => {
21312128
const app = await loadTestingApp({remoteFlags: []})
21322129

21332130
// Then
2134-
expect(app.allExtensions).toHaveLength(8)
2131+
expect(app.allExtensions).toHaveLength(7)
21352132
const extensionsConfig = app.allExtensions.map((ext) => ext.configuration)
21362133
expect(extensionsConfig).toEqual([
2137-
{
2138-
name: 'for-testing-webhooks',
2139-
},
21402134
{
21412135
name: 'for-testing-webhooks',
21422136
},

packages/app/src/cli/models/extensions/extension-instance.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,36 @@ describe('draftMessages', async () => {
570570
})
571571
})
572572

573+
describe('devSessionWatchConfig', () => {
574+
test('returns undefined for extension experience (watch everything)', async () => {
575+
const extensionInstance = await testUIExtension({type: 'ui_extension'})
576+
expect(extensionInstance.devSessionWatchConfig).toBeUndefined()
577+
})
578+
579+
test('returns empty paths for configuration experience (watch nothing)', async () => {
580+
const extensionInstance = await testAppConfigExtensions()
581+
expect(extensionInstance.devSessionWatchConfig).toEqual({paths: []})
582+
})
583+
584+
test('delegates to specification devSessionWatchConfig when defined', async () => {
585+
const config = functionConfiguration()
586+
config.build = {
587+
watch: 'src/**/*.rs',
588+
wasm_opt: true,
589+
}
590+
const extensionInstance = await testFunctionExtension({config, dir: '/tmp/my-function'})
591+
const watchConfig = extensionInstance.devSessionWatchConfig
592+
expect(watchConfig).toBeDefined()
593+
expect(watchConfig!.paths).toContain(joinPath('/tmp/my-function', 'src/**/*.rs'))
594+
})
595+
596+
test('returns undefined for function extension without build.watch', async () => {
597+
const config = functionConfiguration()
598+
const extensionInstance = await testFunctionExtension({config})
599+
expect(extensionInstance.devSessionWatchConfig).toBeUndefined()
600+
})
601+
})
602+
573603
describe('watchedFiles', async () => {
574604
test('returns files based on devSessionWatchPaths when defined', async () => {
575605
await inTemporaryDirectory(async (tmpDir) => {
@@ -607,6 +637,58 @@ describe('watchedFiles', async () => {
607637
})
608638
})
609639

640+
test('respects custom ignore paths from devSessionWatchConfig', async () => {
641+
await inTemporaryDirectory(async (tmpDir) => {
642+
// Given - create an extension with a spec that defines custom ignore paths
643+
const config = functionConfiguration()
644+
config.build = {
645+
watch: '**/*',
646+
wasm_opt: true,
647+
}
648+
const extensionInstance = await testFunctionExtension({
649+
config,
650+
dir: tmpDir,
651+
})
652+
653+
// Override devSessionWatchConfig to include ignore paths
654+
vi.spyOn(extensionInstance, 'devSessionWatchConfig', 'get').mockReturnValue({
655+
paths: [joinPath(tmpDir, '**/*')],
656+
ignore: ['**/ignored-dir/**'],
657+
})
658+
659+
// Create files - one in a normal dir, one in the ignored dir
660+
const srcDir = joinPath(tmpDir, 'src')
661+
const ignoredDir = joinPath(tmpDir, 'ignored-dir')
662+
await mkdir(srcDir)
663+
await mkdir(ignoredDir)
664+
await writeFile(joinPath(srcDir, 'index.js'), 'code')
665+
await writeFile(joinPath(ignoredDir, 'should-be-ignored.js'), 'ignored')
666+
667+
// When
668+
const watchedFiles = extensionInstance.watchedFiles()
669+
670+
// Then
671+
expect(watchedFiles).toContain(joinPath(srcDir, 'index.js'))
672+
expect(watchedFiles).not.toContain(joinPath(ignoredDir, 'should-be-ignored.js'))
673+
})
674+
})
675+
676+
test('returns empty watched files for configuration extensions', async () => {
677+
await inTemporaryDirectory(async (tmpDir) => {
678+
// Given
679+
const extensionInstance = await testAppConfigExtensions(false, tmpDir)
680+
681+
// Create files that should not be watched
682+
await writeFile(joinPath(tmpDir, 'some-file.ts'), 'code')
683+
684+
// When
685+
const watchedFiles = extensionInstance.watchedFiles()
686+
687+
// Then - configuration extensions default to empty paths, so no files watched
688+
expect(watchedFiles).toHaveLength(0)
689+
})
690+
})
691+
610692
test('returns all files when devSessionWatchPaths is undefined', async () => {
611693
await inTemporaryDirectory(async (tmpDir) => {
612694
// Given

packages/app/src/cli/models/extensions/specification.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export interface ExtensionSpecification<TConfiguration extends BaseConfigType =
148148
export interface DevSessionWatchConfig {
149149
/** Absolute paths or globs to watch */
150150
paths: string[]
151-
/** Additional glob patterns to ignore (on top of the default ignore list) */
151+
/** Glob patterns to ignore. When provided, replaces the default ignore list entirely. */
152152
ignore?: string[]
153153
}
154154

packages/app/src/cli/models/extensions/specifications/admin.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import {createExtensionSpecification} from '../specification.js'
2-
import {BaseSchemaWithoutHandle} from '../schemas.js'
2+
import {BaseConfigType, ZodSchemaType} from '../schemas.js'
33
import {zod} from '@shopify/cli-kit/node/schema'
44
import {joinPath} from '@shopify/cli-kit/node/path'
55

6-
const AdminSchema = BaseSchemaWithoutHandle.extend({
6+
const AdminSchema = zod.object({
77
admin: zod
88
.object({
99
static_root: zod.string().optional(),
1010
})
1111
.optional(),
1212
})
1313

14-
const adminSpecificationSpec = createExtensionSpecification({
14+
type AdminConfigType = zod.infer<typeof AdminSchema> & BaseConfigType
15+
16+
const adminSpecificationSpec = createExtensionSpecification<AdminConfigType>({
1517
identifier: 'admin',
1618
uidStrategy: 'single',
1719
experience: 'configuration',
18-
schema: AdminSchema,
20+
schema: AdminSchema as ZodSchemaType<AdminConfigType>,
1921
deployConfig: async (config, _) => {
2022
return {admin: config.admin}
2123
},

packages/app/src/cli/services/dev/app-events/file-watcher.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ describe('file-watcher events', () => {
274274

275275
// Then
276276
expect(watchSpy).toHaveBeenCalledWith([joinPath(dir, '/shopify.app.toml'), joinPath(dir, '/extensions')], {
277+
ignored: ['**/node_modules/**', '**/.git/**'],
277278
ignoreInitial: true,
278279
persistent: true,
279280
})

packages/app/src/cli/services/dev/app-events/file-watcher.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export class FileWatcher {
118118
// Create new watcher
119119
const {default: chokidar} = await import('chokidar')
120120
this.watcher = chokidar.watch(watchPaths, {
121+
ignored: ['**/node_modules/**', '**/.git/**'],
121122
persistent: true,
122123
ignoreInitial: true,
123124
})

0 commit comments

Comments
 (0)