@tywalk/pcf-helper
Version:
Command line helper for building and publishing PCF controls to Dataverse.
250 lines (249 loc) • 11.8 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 init_pcf_1 = require("../tasks/init-pcf");
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const color_logger_1 = __importDefault(require("@tywalk/color-logger"));
jest.mock('child_process');
jest.mock('fs');
jest.mock('@tywalk/color-logger', () => ({
__esModule: true,
default: {
log: jest.fn(),
success: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
debug: jest.fn()
}
}));
const mockSpawnSync = child_process_1.spawnSync;
const mockFs = fs;
describe('init-pcf task', () => {
beforeEach(() => {
jest.clearAllMocks();
mockSpawnSync.mockReturnValue({
status: 0,
signal: null
});
mockFs.readdirSync.mockReturnValue([]);
mockFs.existsSync.mockReturnValue(false);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('runInit validation', () => {
it('should return error code 1 for invalid template', () => {
const result = (0, init_pcf_1.runInit)('/test/path', 'myControl', 'MyPublisher', 'mp', 'invalid-template', 'react', true, false);
expect(result).toBe(1);
expect(color_logger_1.default.error).toHaveBeenCalledWith(expect.stringContaining('Invalid template'));
});
it('should return error code 1 for invalid framework', () => {
const result = (0, init_pcf_1.runInit)('/test/path', 'myControl', 'MyPublisher', 'mp', 'field', 'invalid-framework', true, false);
expect(result).toBe(1);
expect(color_logger_1.default.error).toHaveBeenCalledWith(expect.stringContaining('Invalid framework'));
});
it('should accept valid templates: field and dataset', () => {
mockSpawnSync.mockReturnValue({
status: 0,
signal: null
});
mockFs.readdirSync.mockReturnValue([]);
const resultField = (0, init_pcf_1.runInit)('/test/path', 'myControl', 'MyPublisher', 'mp', 'field', 'react', true, false);
// Should proceed past validation (might fail later on spawn, but not validation)
expect(color_logger_1.default.error).not.toHaveBeenCalledWith(expect.stringContaining('Invalid template'));
});
it('should accept valid frameworks: none and react', () => {
mockSpawnSync.mockReturnValue({
status: 0,
signal: null
});
const resultNone = (0, init_pcf_1.runInit)('/test/path', 'myControl', 'MyPublisher', 'mp', 'field', 'none', true, false);
expect(color_logger_1.default.error).not.toHaveBeenCalledWith(expect.stringContaining('Invalid framework'));
});
it('should use default path from process.cwd() if not provided', () => {
const cwdSpy = jest.spyOn(process, 'cwd').mockReturnValue('/default/path');
mockSpawnSync.mockReturnValue({
status: 0,
signal: null
});
mockFs.readdirSync.mockReturnValue([]);
(0, init_pcf_1.runInit)('', 'myControl', 'MyPublisher', 'mp', 'field', 'react', true, false);
expect(cwdSpy).toHaveBeenCalled();
cwdSpy.mockRestore();
});
it('should pass correct parameters to pac pcf init command', () => {
mockSpawnSync.mockReturnValue({
status: 0,
signal: null
});
mockFs.readdirSync.mockReturnValue([]);
(0, init_pcf_1.runInit)('/test/path', 'MyControl', 'MyPublisher', 'mp', 'dataset', 'react', true, false);
expect(mockSpawnSync).toHaveBeenCalledWith('pac', expect.arrayContaining([
'pcf',
'init',
'-ns', 'mp',
'-n', 'MyControl',
'-t', 'dataset',
'-fw', 'react',
'-o', '/test/path',
'-npm', 'true'
]), expect.any(Object));
});
it('should pass npm false when npm install is disabled', () => {
mockSpawnSync.mockReturnValue({
status: 0,
signal: null
});
mockFs.readdirSync.mockReturnValue([]);
(0, init_pcf_1.runInit)('/test/path', 'MyControl', 'MyPublisher', 'mp', 'field', 'react', false, false);
expect(mockSpawnSync).toHaveBeenCalledWith('pac', expect.arrayContaining(['pcf', 'init', '-npm', 'false']), expect.any(Object));
});
it('should return error if pac pcf init fails', () => {
mockSpawnSync.mockReturnValue({
status: 1,
signal: null,
error: new Error('init failed')
});
const result = (0, init_pcf_1.runInit)('/test/path', 'myControl', 'MyPublisher', 'mp', 'field', 'react', true, false);
// Should exit with error from init task
expect(result).toBe(1);
});
});
describe('pcfExistsInParent logic', () => {
it('should detect .pcfproj file at current level', () => {
mockSpawnSync.mockReturnValue({
status: 0,
signal: null
});
// First call (at root) returns .pcfproj file
mockFs.readdirSync
.mockReturnValueOnce(['.pcfproj'])
.mockReturnValueOnce(['.pcfproj', 'Solutions']);
mockFs.existsSync.mockReturnValue(true);
mockFs.realpathSync.mockReturnValue('/test/path');
(0, init_pcf_1.runInit)('/test/path', 'myControl', 'MyPublisher', 'mp', 'field', 'react', true, false);
// Should find PCF project at root
expect(mockFs.readdirSync).toHaveBeenCalled();
});
it('should throw error if PCF project not found within 3 levels', () => {
mockSpawnSync.mockReturnValue({
status: 0,
signal: null
});
// No .pcfproj found at any level
mockFs.readdirSync.mockReturnValue(['package.json', 'tsconfig.json']);
const result = (0, init_pcf_1.runInit)('/deep/nested/path', 'myControl', 'MyPublisher', 'mp', 'field', 'react', true, false);
// Should return error
expect(result).toBe(1);
expect(color_logger_1.default.error).toHaveBeenCalledWith(expect.stringContaining('Unable to locate PCF project'));
});
it('should create Solutions folder path if PCF exists at root', () => {
mockSpawnSync.mockReturnValue({
status: 0,
signal: null
});
// PCF exists at root
mockFs.readdirSync
.mockReturnValueOnce(['control.pcfproj'])
.mockReturnValueOnce(['control.pcfproj', 'Solutions']);
mockFs.existsSync.mockReturnValue(true);
mockFs.realpathSync.mockReturnValue('/test/path');
(0, init_pcf_1.runInit)('/test/path', 'myControl', 'MyPublisher', 'mp', 'field', 'react', true, false);
const callArgs = mockSpawnSync.mock.calls.find((call) => call[0] === 'pac' && Array.isArray(call[1]) && call[1][0] === 'solution' && call[1][1] === 'init');
expect(callArgs).toBeDefined();
if (callArgs) {
const hasSolutionsPath = callArgs[1].some((arg) => arg.includes('Solutions'));
expect(hasSolutionsPath).toBe(true);
}
});
});
describe('solution initialization', () => {
it('should create solution if pcf init succeeds', () => {
mockSpawnSync.mockReturnValue({
status: 0,
signal: null
});
mockFs.readdirSync.mockReturnValue(['control.pcfproj']);
mockFs.existsSync.mockReturnValue(true);
mockFs.realpathSync.mockReturnValue('/test/path');
(0, init_pcf_1.runInit)('/test/path', 'myControl', 'MyPublisher', 'mp', 'field', 'react', true, false);
const solutionInitCall = mockSpawnSync.mock.calls.find((call) => call[0] === 'pac' && Array.isArray(call[1]) && call[1][0] === 'solution' && call[1][1] === 'init');
expect(solutionInitCall).toBeDefined();
});
it('should add solution reference to pcf project', () => {
mockSpawnSync.mockReturnValue({
status: 0,
signal: null
});
mockFs.readdirSync.mockReturnValue(['control.pcfproj']);
mockFs.existsSync.mockReturnValue(true);
mockFs.realpathSync.mockReturnValue('/test/path');
(0, init_pcf_1.runInit)('/test/path', 'myControl', 'MyPublisher', 'mp', 'field', 'react', true, false);
const hasAddRefCall = mockSpawnSync.mock.calls.some((call) => call[0] === 'pac' && Array.isArray(call[1]) && call[1][0] === 'solution' && call[1][1] === 'add-reference');
expect(hasAddRefCall).toBe(true);
});
it('should return error if solution init fails', () => {
mockSpawnSync
.mockReturnValueOnce({
status: 0,
signal: null
})
.mockReturnValueOnce({
status: 1,
signal: null,
error: new Error('solution init failed')
});
mockFs.readdirSync.mockReturnValue(['.pcfproj']);
const result = (0, init_pcf_1.runInit)('/test/path', 'myControl', 'MyPublisher', 'mp', 'field', 'react', true, false);
expect(result).toBe(1);
});
});
describe('verbose logging', () => {
it('should not log error details when verbose is false', () => {
mockSpawnSync.mockReturnValue({
status: 1,
signal: null,
error: new Error('some error')
});
mockFs.readdirSync.mockReturnValue([]);
(0, init_pcf_1.runInit)('/test/path', 'myControl', 'MyPublisher', 'mp', 'field', 'react', true, false);
expect(color_logger_1.default.debug).not.toHaveBeenCalledWith(expect.stringContaining('stack'));
});
});
});