mirror of https://github.com/openedx/paragon.git
285 lines
8.9 KiB
JavaScript
285 lines
8.9 KiB
JavaScript
const http = require('http');
|
|
const fs = require('fs');
|
|
|
|
const serveThemeCssCommand = require('../serve-theme-css');
|
|
|
|
jest.mock('fs');
|
|
jest.mock('http');
|
|
jest.mock('ora', () => jest.fn(() => ({
|
|
start: jest.fn().mockReturnThis(),
|
|
succeed: jest.fn((message) => {
|
|
// Make ora.succeed call console.log so we can capture it
|
|
// eslint-disable-next-line no-console
|
|
console.log(message);
|
|
return this;
|
|
}),
|
|
fail: jest.fn().mockReturnThis(),
|
|
})));
|
|
|
|
describe('serveThemeCssCommand', () => {
|
|
let mockServer;
|
|
let mockListen;
|
|
let mockClose;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
|
|
mockListen = jest.fn((port, host, callback) => {
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
});
|
|
mockClose = jest.fn((callback) => {
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
});
|
|
mockServer = {
|
|
listen: mockListen,
|
|
on: jest.fn(),
|
|
close: mockClose,
|
|
};
|
|
|
|
http.createServer.mockReturnValue(mockServer);
|
|
fs.existsSync.mockReturnValue(true);
|
|
fs.statSync.mockReturnValue({
|
|
isDirectory: () => false,
|
|
size: 1024,
|
|
mtime: { getTime: () => 1234567890 },
|
|
});
|
|
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
themeUrls: {
|
|
core: {
|
|
paths: {
|
|
default: './core.css',
|
|
minified: './core.min.css',
|
|
},
|
|
},
|
|
defaults: {
|
|
light: 'light',
|
|
},
|
|
variants: {
|
|
light: {
|
|
paths: {
|
|
default: './light.css',
|
|
minified: './light.min.css',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it('should start server with default arguments', async () => {
|
|
const args = [];
|
|
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
|
|
await serveThemeCssCommand(args);
|
|
|
|
expect(http.createServer).toHaveBeenCalled();
|
|
expect(mockListen).toHaveBeenCalledWith(3000, 'localhost', expect.any(Function));
|
|
|
|
// Wait for the setTimeout to complete and output to be generated
|
|
await new Promise((resolve) => { setTimeout(resolve, 1100); });
|
|
|
|
// Verify that the default docs URL is included in the output
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('https://paragon-openedx.netlify.app/'),
|
|
);
|
|
});
|
|
|
|
it('should exit if build directory does not exist', async () => {
|
|
const args = ['--build-dir=./nonexistent'];
|
|
fs.existsSync.mockReturnValue(false);
|
|
|
|
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
|
|
const mockConsoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
await serveThemeCssCommand(args);
|
|
|
|
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
expect.stringContaining('Error: Build directory'),
|
|
);
|
|
expect(mockExit).toHaveBeenCalledWith(1);
|
|
});
|
|
|
|
it('should exit if theme-urls.json does not exist', async () => {
|
|
const args = ['--build-dir=./dist'];
|
|
// Mock that dist exists but theme-urls.json doesn't
|
|
fs.existsSync.mockImplementation((path) => !path.endsWith('theme-urls.json'));
|
|
|
|
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
|
|
const mockConsoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
await serveThemeCssCommand(args);
|
|
|
|
// The actual implementation calls console.error twice with separate messages
|
|
expect(mockConsoleError).toHaveBeenNthCalledWith(
|
|
1,
|
|
expect.stringContaining('Error:'),
|
|
);
|
|
expect(mockConsoleError).toHaveBeenNthCalledWith(
|
|
1,
|
|
expect.stringContaining('does not appear to be a valid Paragon dist directory'),
|
|
);
|
|
expect(mockConsoleError).toHaveBeenNthCalledWith(
|
|
2,
|
|
expect.stringContaining('Missing theme-urls.json file'),
|
|
);
|
|
expect(mockExit).toHaveBeenCalledWith(1);
|
|
});
|
|
|
|
it('should exit if theme-urls.json is invalid JSON', async () => {
|
|
const args = ['--build-dir=./dist'];
|
|
fs.readFileSync.mockReturnValue('invalid json');
|
|
|
|
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
|
|
const mockConsoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
await serveThemeCssCommand(args);
|
|
|
|
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
expect.stringContaining('Error: Could not read theme-urls.json file.'),
|
|
);
|
|
expect(mockExit).toHaveBeenCalledWith(1);
|
|
});
|
|
|
|
it('should handle server errors', async () => {
|
|
const args = ['--port=3000'];
|
|
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
|
|
const mockConsoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
await serveThemeCssCommand(args);
|
|
|
|
// Simulate server error
|
|
const errorCallback = mockServer.on.mock.calls.find(call => call[0] === 'error')[1];
|
|
errorCallback({ code: 'EADDRINUSE' });
|
|
|
|
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
expect.stringContaining('Error: Port 3000 is already in use.'),
|
|
);
|
|
expect(mockExit).toHaveBeenCalledWith(1);
|
|
});
|
|
|
|
it('should handle graceful shutdown', async () => {
|
|
const args = [];
|
|
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
|
|
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
|
|
await serveThemeCssCommand(args);
|
|
|
|
// Simulate SIGINT
|
|
const sigintCallback = process.listeners('SIGINT').pop();
|
|
sigintCallback();
|
|
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('Shutting down server...'),
|
|
);
|
|
expect(mockClose).toHaveBeenCalled();
|
|
expect(mockExit).toHaveBeenCalledWith(0);
|
|
});
|
|
|
|
it('should parse command line arguments correctly', async () => {
|
|
const args = [
|
|
'-b', './custom-dist',
|
|
'-p', '8080',
|
|
'-h', '0.0.0.0',
|
|
'--cors=false',
|
|
];
|
|
|
|
await serveThemeCssCommand(args);
|
|
|
|
expect(mockListen).toHaveBeenCalledWith(8080, '0.0.0.0', expect.any(Function));
|
|
});
|
|
|
|
it('should include custom docs URL in output when specified', async () => {
|
|
const args = [
|
|
'--docs-url=https://custom-docs.example.com',
|
|
'--theme-name=Custom Theme',
|
|
];
|
|
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
|
|
await serveThemeCssCommand(args);
|
|
|
|
expect(http.createServer).toHaveBeenCalled();
|
|
expect(mockListen).toHaveBeenCalledWith(3000, 'localhost', expect.any(Function));
|
|
|
|
// Wait for the setTimeout to complete and output to be generated
|
|
await new Promise((resolve) => { setTimeout(resolve, 1100); });
|
|
|
|
// Verify that the custom docs URL is included in the output
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('https://custom-docs.example.com'),
|
|
);
|
|
});
|
|
|
|
it('should display server URL and available theme files', async () => {
|
|
const args = [];
|
|
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
|
|
await serveThemeCssCommand(args);
|
|
|
|
expect(http.createServer).toHaveBeenCalled();
|
|
expect(mockListen).toHaveBeenCalledWith(3000, 'localhost', expect.any(Function));
|
|
|
|
// Wait for the setTimeout to complete and output to be generated
|
|
await new Promise((resolve) => { setTimeout(resolve, 1100); });
|
|
|
|
// Verify server URL is displayed
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('Theme CSS server running at http://localhost:3000'),
|
|
);
|
|
|
|
// Verify available theme files section is displayed
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('Available theme files:'),
|
|
);
|
|
|
|
// Verify core CSS files are listed
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('Core CSS:'),
|
|
);
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('http://localhost:3000/core.css'),
|
|
);
|
|
|
|
// Verify theme variants are listed
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('Theme Variants:'),
|
|
);
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('light:'),
|
|
);
|
|
|
|
// Verify theme configuration is listed
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('Theme Configuration:'),
|
|
);
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('http://localhost:3000/theme-urls.json'),
|
|
);
|
|
});
|
|
|
|
it('should display shutdown instructions', async () => {
|
|
const args = [];
|
|
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
|
|
await serveThemeCssCommand(args);
|
|
|
|
expect(http.createServer).toHaveBeenCalled();
|
|
expect(mockListen).toHaveBeenCalledWith(3000, 'localhost', expect.any(Function));
|
|
|
|
// Wait for the setTimeout to complete and output to be generated
|
|
await new Promise((resolve) => { setTimeout(resolve, 1100); });
|
|
|
|
// Verify shutdown instructions are displayed
|
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('Press Ctrl+C to stop the server'),
|
|
);
|
|
});
|
|
});
|