lib/core-server/src/utils/watch-story-specifiers.test.ts
import { normalizeStoriesEntry } from '@storybook/core-common';
import path from 'path';
import Watchpack from 'watchpack';
import { watchStorySpecifiers } from './watch-story-specifiers';
jest.mock('watchpack');
describe('watchStorySpecifiers', () => {
const workingDir = path.join(__dirname, '__mockdata__');
const options = {
configDir: path.join(workingDir, '.storybook'),
workingDir,
};
let close: () => void;
afterEach(() => close?.());
it('watches basic globs', async () => {
const specifier = normalizeStoriesEntry('../src/**/*.stories.@(ts|js)', options);
const onInvalidate = jest.fn();
close = watchStorySpecifiers([specifier], { workingDir }, onInvalidate);
expect(Watchpack).toHaveBeenCalledTimes(1);
const watcher = Watchpack.mock.instances[0];
expect(watcher.watch).toHaveBeenCalledWith({ directories: ['./src'] });
expect(watcher.on).toHaveBeenCalledTimes(2);
const onChange = watcher.on.mock.calls[0][1];
const onRemove = watcher.on.mock.calls[1][1];
// File changed, matching
onInvalidate.mockClear();
await onChange('src/nested/Button.stories.ts', 1234);
expect(onInvalidate).toHaveBeenCalledWith(specifier, `./src/nested/Button.stories.ts`, false);
// File changed, NOT matching
onInvalidate.mockClear();
await onChange('src/nested/Button.ts', 1234);
expect(onInvalidate).not.toHaveBeenCalled();
// File removed, matching
onInvalidate.mockClear();
await onRemove('src/nested/Button.stories.ts');
expect(onInvalidate).toHaveBeenCalledWith(specifier, `./src/nested/Button.stories.ts`, true);
// File removed, NOT matching
onInvalidate.mockClear();
await onRemove('src/nested/Button.ts');
expect(onInvalidate).not.toHaveBeenCalled();
// File moved out, matching
onInvalidate.mockClear();
await onChange('src/nested/Button.stories.ts', null);
expect(onInvalidate).toHaveBeenCalledWith(specifier, `./src/nested/Button.stories.ts`, true);
// File renamed, matching
onInvalidate.mockClear();
await onChange('src/nested/Button.stories.ts', null);
await onChange('src/nested/Button-2.stories.ts', 1234);
expect(onInvalidate).toHaveBeenCalledWith(specifier, `./src/nested/Button.stories.ts`, true);
expect(onInvalidate).toHaveBeenCalledWith(specifier, `./src/nested/Button-2.stories.ts`, false);
});
it('scans directories when they are added', async () => {
const specifier = normalizeStoriesEntry('../src/**/*.stories.@(ts|js)', options);
const onInvalidate = jest.fn();
close = watchStorySpecifiers([specifier], { workingDir }, onInvalidate);
expect(Watchpack).toHaveBeenCalledTimes(1);
const watcher = Watchpack.mock.instances[0];
expect(watcher.watch).toHaveBeenCalledWith({ directories: ['./src'] });
expect(watcher.on).toHaveBeenCalledTimes(2);
const onChange = watcher.on.mock.calls[0][1];
onInvalidate.mockClear();
await onChange('src/nested', 1234);
expect(onInvalidate).toHaveBeenCalledWith(specifier, `./src/nested/Button.stories.ts`, false);
});
it('watches single file globs', async () => {
const specifier = normalizeStoriesEntry('../src/nested/Button.stories.mdx', options);
const onInvalidate = jest.fn();
close = watchStorySpecifiers([specifier], { workingDir }, onInvalidate);
expect(Watchpack).toHaveBeenCalledTimes(1);
const watcher = Watchpack.mock.instances[0];
expect(watcher.watch).toHaveBeenCalledWith({ directories: ['./src/nested'] });
expect(watcher.on).toHaveBeenCalledTimes(2);
const onChange = watcher.on.mock.calls[0][1];
const onRemove = watcher.on.mock.calls[1][1];
// File changed, matching
onInvalidate.mockClear();
await onChange('src/nested/Button.stories.mdx', 1234);
expect(onInvalidate).toHaveBeenCalledWith(specifier, `./src/nested/Button.stories.mdx`, false);
// File changed, NOT matching
onInvalidate.mockClear();
await onChange('src/nested/Button.mdx', 1234);
expect(onInvalidate).not.toHaveBeenCalled();
// File removed, matching
onInvalidate.mockClear();
await onRemove('src/nested/Button.stories.mdx');
expect(onInvalidate).toHaveBeenCalledWith(specifier, `./src/nested/Button.stories.mdx`, true);
// File removed, NOT matching
onInvalidate.mockClear();
await onRemove('src/nested/Button.mdx');
expect(onInvalidate).not.toHaveBeenCalled();
// File moved out, matching
onInvalidate.mockClear();
await onChange('src/nested/Button.stories.mdx', null);
expect(onInvalidate).toHaveBeenCalledWith(specifier, `./src/nested/Button.stories.mdx`, true);
});
it('multiplexes between two specifiers on the same directory', async () => {
const globSpecifier = normalizeStoriesEntry('../src/**/*.stories.@(ts|js)', options);
const fileSpecifier = normalizeStoriesEntry('../src/nested/Button.stories.mdx', options);
const onInvalidate = jest.fn();
close = watchStorySpecifiers([globSpecifier, fileSpecifier], { workingDir }, onInvalidate);
expect(Watchpack).toHaveBeenCalledTimes(1);
const watcher = Watchpack.mock.instances[0];
expect(watcher.watch).toHaveBeenCalledWith({ directories: ['./src', './src/nested'] });
expect(watcher.on).toHaveBeenCalledTimes(2);
const onChange = watcher.on.mock.calls[0][1];
onInvalidate.mockClear();
await onChange('src/nested/Button.stories.ts', 1234);
expect(onInvalidate).toHaveBeenCalledWith(
globSpecifier,
`./src/nested/Button.stories.ts`,
false
);
onInvalidate.mockClear();
await onChange('src/nested/Button.stories.mdx', 1234);
expect(onInvalidate).toHaveBeenCalledWith(
fileSpecifier,
`./src/nested/Button.stories.mdx`,
false
);
});
});