@posthog/wizard
Version:
The PostHog wizard helps you to configure your project
208 lines (200 loc) • 9.48 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const path_1 = __importDefault(require("path"));
const add_editor_rules_1 = require("../add-editor-rules");
const analytics_1 = require("../../utils/analytics");
const clack_1 = __importDefault(require("../../utils/clack"));
const chalk_1 = __importDefault(require("chalk"));
// Mock dependencies
jest.mock('fs', () => ({
promises: {
mkdir: jest.fn(),
readFile: jest.fn(),
writeFile: jest.fn(),
},
}));
jest.mock('../../utils/analytics', () => ({
analytics: {
capture: jest.fn(),
setTag: jest.fn(),
shutdown: jest.fn().mockResolvedValue(undefined),
},
}));
jest.mock('../../utils/clack', () => ({
log: {
info: jest.fn(),
},
select: jest.fn().mockResolvedValue(true),
isCancel: jest.fn((value) => false),
cancel: jest.fn(),
}));
describe('addEditorRules', () => {
const mockOptions = {
installDir: '/test/dir',
rulesName: 'react-rules.md',
integration: 'react',
};
const originalEnv = process.env;
const mkdirMock = fs.promises.mkdir;
const readFileMock = fs.promises.readFile;
const writeFileMock = fs.promises.writeFile;
// eslint-disable-next-line @typescript-eslint/unbound-method
const captureMock = analytics_1.analytics.capture;
const infoMock = clack_1.default.log.info;
const selectMock = clack_1.default.select;
const isCancelMock = clack_1.default.isCancel;
const cancelMock = clack_1.default.cancel;
beforeEach(() => {
// Reset all mocks before each test
jest.clearAllMocks();
// Clear mock implementations
fs.promises.mkdir.mockReset();
fs.promises.readFile.mockReset();
fs.promises.writeFile.mockReset();
selectMock.mockReset();
selectMock.mockResolvedValue(true); // Default to "Yes" for the prompt
isCancelMock.mockReset();
isCancelMock.mockReturnValue(false);
cancelMock.mockReset();
process.env = { ...originalEnv };
});
afterAll(() => {
process.env = originalEnv;
});
it('should not install rules when CURSOR_TRACE_ID is not set', async () => {
delete process.env.CURSOR_TRACE_ID;
await (0, add_editor_rules_1.addEditorRulesStep)(mockOptions);
expect(mkdirMock).not.toHaveBeenCalled();
expect(readFileMock).not.toHaveBeenCalled();
expect(writeFileMock).not.toHaveBeenCalled();
expect(captureMock).not.toHaveBeenCalled();
expect(infoMock).not.toHaveBeenCalled();
});
it('should install rules when CURSOR_TRACE_ID is set', async () => {
process.env.CURSOR_TRACE_ID = 'test-trace-id';
const mockFrameworkRules = 'framework rules {universal} content';
const mockUniversalRules = 'universal rules content';
const expectedCombinedRules = 'framework rules universal rules content content';
fs.promises.readFile
.mockImplementationOnce(() => Promise.resolve(mockFrameworkRules))
.mockImplementationOnce(() => Promise.resolve(mockUniversalRules));
await (0, add_editor_rules_1.addEditorRulesStep)(mockOptions);
// Check if directory was created
expect(mkdirMock).toHaveBeenCalledWith(path_1.default.join('/test/dir', '.cursor', 'rules'), { recursive: true });
// Check if correct files were read
expect(readFileMock).toHaveBeenCalledTimes(2);
expect(readFileMock).toHaveBeenNthCalledWith(1, expect.stringMatching(/react-rules\.md$/), 'utf8');
expect(readFileMock).toHaveBeenNthCalledWith(2, expect.stringMatching(/universal\.md$/), 'utf8');
// Check if combined rules were written correctly
expect(writeFileMock).toHaveBeenCalledWith(path_1.default.join('/test/dir', '.cursor', 'rules', 'posthog-integration.mdc'), expectedCombinedRules, 'utf8');
// Check if analytics were captured
expect(captureMock).toHaveBeenCalledWith('wizard interaction', {
action: 'added editor rules',
integration: mockOptions.integration,
});
// Check if success message was logged
expect(infoMock).toHaveBeenCalledWith(`Added Cursor rules to ${chalk_1.default.bold.cyan('.cursor/rules/posthog-integration.mdc')}`);
});
it('should handle file system errors gracefully', async () => {
process.env.CURSOR_TRACE_ID = 'test-trace-id';
const mockError = new Error('File not found');
// Mock readFile to throw an error
fs.promises.readFile.mockRejectedValue(mockError);
await expect((0, add_editor_rules_1.addEditorRulesStep)(mockOptions)).rejects.toThrow(mockError);
expect(writeFileMock).not.toHaveBeenCalled();
expect(captureMock).not.toHaveBeenCalled();
expect(infoMock).not.toHaveBeenCalled();
});
it('should handle missing rules files gracefully', async () => {
process.env.CURSOR_TRACE_ID = 'test-trace-id';
const mockError = new Error('File system error');
// Mock mkdir to throw an error
fs.promises.mkdir.mockRejectedValue(mockError);
await expect((0, add_editor_rules_1.addEditorRulesStep)(mockOptions)).rejects.toThrow(mockError);
expect(writeFileMock).not.toHaveBeenCalled();
expect(captureMock).not.toHaveBeenCalled();
expect(infoMock).not.toHaveBeenCalled();
});
it('should correctly substitute universal rules with realistic content', async () => {
process.env.CURSOR_TRACE_ID = 'test-trace-id';
const mockFrameworkRules = `---
description: apply when interacting with PostHog/analytics tasks
globs:
alwaysApply: true
---
{universal}
# Framework-specific rules
- Rule 1
- Rule 2`;
const mockUniversalRules = `Never hallucinate an API key. Instead, always use the API key populated in the .env file.
# Feature flags
A given feature flag should be used in as few places as possible. Do not increase the risk of undefined behavior by scattering the same feature flag across multiple areas of code.`;
const expectedCombinedRules = `---
description: apply when interacting with PostHog/analytics tasks
globs:
alwaysApply: true
---
Never hallucinate an API key. Instead, always use the API key populated in the .env file.
# Feature flags
A given feature flag should be used in as few places as possible. Do not increase the risk of undefined behavior by scattering the same feature flag across multiple areas of code.
# Framework-specific rules
- Rule 1
- Rule 2`;
fs.promises.readFile
.mockImplementationOnce(() => Promise.resolve(mockFrameworkRules))
.mockImplementationOnce(() => Promise.resolve(mockUniversalRules));
await (0, add_editor_rules_1.addEditorRulesStep)(mockOptions);
// Check if directory was created
expect(mkdirMock).toHaveBeenCalledWith(path_1.default.join('/test/dir', '.cursor', 'rules'), { recursive: true });
// Check if correct files were read
expect(readFileMock).toHaveBeenCalledWith(expect.stringMatching(/react-rules\.md$/), 'utf8');
expect(readFileMock).toHaveBeenCalledWith(expect.stringMatching(/universal\.md$/), 'utf8');
// Check if combined rules were written correctly
expect(writeFileMock).toHaveBeenCalledWith(path_1.default.join('/test/dir', '.cursor', 'rules', 'posthog-integration.mdc'), expectedCombinedRules, 'utf8');
// Check if analytics were captured
expect(captureMock).toHaveBeenCalledWith('wizard interaction', {
action: 'added editor rules',
integration: mockOptions.integration,
});
// Check if success message was logged
expect(infoMock).toHaveBeenCalledWith(`Added Cursor rules to ${chalk_1.default.bold.cyan('.cursor/rules/posthog-integration.mdc')}`);
});
});
//# sourceMappingURL=add-editor-rules.test.js.map