@openedx/paragon 23.5.1 → 23.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/paragon-scripts.js +5 -1
- package/dist/light.css +1 -1
- package/dist/light.css.map +1 -1
- package/dist/light.min.css +1 -1
- package/lib/__tests__/build-scss.test.js +146 -0
- package/lib/__tests__/build-tokens.test.js +106 -0
- package/lib/__tests__/help.test.js +301 -0
- package/lib/__tests__/install-theme.test.js +66 -0
- package/lib/__tests__/migrate-to-openedx-scope.test.js +106 -0
- package/lib/__tests__/replace-variables.test.js +79 -0
- package/lib/__tests__/utils.test.js +87 -0
- package/lib/__tests__/version.test.js +20 -0
- package/lib/build-scss.js +1 -0
- package/lib/help.js +4 -1
- package/package.json +2 -1
- package/styles/css/themes/light/variables.css +1 -1
- package/tokens/__mocks__/style-dictionary.js +24 -0
- package/tokens/src/themes/light/components/Menu.json +1 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const migrateToOpenEdxScopeCommand = require('../migrate-to-openedx-scope');
|
|
5
|
+
|
|
6
|
+
jest.mock('fs');
|
|
7
|
+
jest.mock('path');
|
|
8
|
+
|
|
9
|
+
describe('migrateToOpenEdxScopeCommand', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
|
|
13
|
+
process.argv = ['node', 'script', 'command', '/mock/project/path'];
|
|
14
|
+
|
|
15
|
+
path.resolve.mockReturnValue('/mock/project/path');
|
|
16
|
+
path.join.mockImplementation((...args) => args.join('/'));
|
|
17
|
+
path.basename.mockImplementation((filePath) => filePath.split('/').pop());
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should process valid files and replace @edx/paragon with @openedx/paragon', () => {
|
|
21
|
+
const mockFiles = {
|
|
22
|
+
'/mock/project/path': ['file1.js', 'file2.tsx', '.git', 'node_modules', 'src'],
|
|
23
|
+
'/mock/project/path/src': ['component.jsx'],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const mockFileContents = {
|
|
27
|
+
'/mock/project/path/file1.js': 'import { Button } from "@edx/paragon";',
|
|
28
|
+
'/mock/project/path/file2.tsx': 'import { Modal } from "@edx/paragon/modal";',
|
|
29
|
+
'/mock/project/path/src/component.jsx': 'require("@edx/paragon")',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
fs.readdirSync.mockImplementation((dir) => mockFiles[dir] || []);
|
|
33
|
+
fs.statSync.mockImplementation((filePath) => ({
|
|
34
|
+
isDirectory: () => !filePath.includes('.'),
|
|
35
|
+
}));
|
|
36
|
+
fs.readFileSync.mockImplementation((filePath) => mockFileContents[filePath] || '');
|
|
37
|
+
fs.writeFileSync.mockImplementation(() => {});
|
|
38
|
+
|
|
39
|
+
migrateToOpenEdxScopeCommand();
|
|
40
|
+
|
|
41
|
+
expect(fs.writeFileSync).toHaveBeenCalledTimes(3);
|
|
42
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
43
|
+
'/mock/project/path/file1.js',
|
|
44
|
+
'import { Button } from "@openedx/paragon";',
|
|
45
|
+
'utf-8',
|
|
46
|
+
);
|
|
47
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
48
|
+
'/mock/project/path/file2.tsx',
|
|
49
|
+
'import { Modal } from "@openedx/paragon/modal";',
|
|
50
|
+
'utf-8',
|
|
51
|
+
);
|
|
52
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
53
|
+
'/mock/project/path/src/component.jsx',
|
|
54
|
+
'require("@openedx/paragon")',
|
|
55
|
+
'utf-8',
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should skip invalid file extensions', () => {
|
|
60
|
+
const mockFiles = {
|
|
61
|
+
'/mock/project/path': ['file1.txt', 'file2.json'],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
fs.readdirSync.mockImplementation((dir) => mockFiles[dir] || []);
|
|
65
|
+
fs.statSync.mockImplementation(() => ({
|
|
66
|
+
isDirectory: () => false,
|
|
67
|
+
}));
|
|
68
|
+
fs.readFileSync.mockImplementation(() => 'import "@edx/paragon"');
|
|
69
|
+
|
|
70
|
+
migrateToOpenEdxScopeCommand();
|
|
71
|
+
|
|
72
|
+
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should skip node_modules directory', () => {
|
|
76
|
+
const mockFiles = {
|
|
77
|
+
'/mock/project/path': ['node_modules'],
|
|
78
|
+
'/mock/project/path/node_modules': ['some-package'],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
fs.readdirSync.mockImplementation((dir) => mockFiles[dir] || []);
|
|
82
|
+
fs.statSync.mockImplementation(() => ({
|
|
83
|
+
isDirectory: () => true,
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
migrateToOpenEdxScopeCommand();
|
|
87
|
+
|
|
88
|
+
expect(fs.readdirSync).not.toHaveBeenCalledWith('/mock/project/path/node_modules');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should skip hidden directories except . and ..', () => {
|
|
92
|
+
const mockFiles = {
|
|
93
|
+
'/mock/project/path': ['.git', '.', '..', '.hidden'],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
fs.readdirSync.mockImplementation((dir) => mockFiles[dir] || []);
|
|
97
|
+
fs.statSync.mockImplementation(() => ({
|
|
98
|
+
isDirectory: () => true,
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
migrateToOpenEdxScopeCommand();
|
|
102
|
+
|
|
103
|
+
expect(fs.readdirSync).not.toHaveBeenCalledWith('/mock/project/path/.git');
|
|
104
|
+
expect(fs.readdirSync).not.toHaveBeenCalledWith('/mock/project/path/.hidden');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const replaceVariablesCommand = require('../replace-variables');
|
|
2
|
+
const { transformInPath } = require('../../tokens/utils');
|
|
3
|
+
const mapSCSStoCSS = require('../../tokens/map-scss-to-css');
|
|
4
|
+
|
|
5
|
+
jest.mock('../../tokens/utils');
|
|
6
|
+
jest.mock('../../tokens/map-scss-to-css');
|
|
7
|
+
|
|
8
|
+
describe('replaceVariablesCommand', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
|
|
12
|
+
mapSCSStoCSS.mockReturnValue({
|
|
13
|
+
'$primary-color': '#123456',
|
|
14
|
+
'$secondary-color': '#654321',
|
|
15
|
+
});
|
|
16
|
+
transformInPath.mockResolvedValue();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should call transformInPath with correct arguments for default replacement', async () => {
|
|
20
|
+
const args = ['--filePath', 'styles.scss', '--source', 'variables.scss'];
|
|
21
|
+
|
|
22
|
+
await replaceVariablesCommand(args);
|
|
23
|
+
|
|
24
|
+
expect(mapSCSStoCSS).toHaveBeenCalledWith('variables.scss');
|
|
25
|
+
expect(transformInPath).toHaveBeenCalledWith(
|
|
26
|
+
'styles.scss',
|
|
27
|
+
{ '$primary-color': '#123456', '$secondary-color': '#654321' },
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should handle usage replacement type with direction', async () => {
|
|
32
|
+
const args = [
|
|
33
|
+
'--filePath', 'styles.scss',
|
|
34
|
+
'--source', 'variables.scss',
|
|
35
|
+
'--replacementType', 'usage',
|
|
36
|
+
'--direction', 'forward',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
await replaceVariablesCommand(args);
|
|
40
|
+
|
|
41
|
+
expect(transformInPath).toHaveBeenCalledWith(
|
|
42
|
+
'styles.scss',
|
|
43
|
+
{ '$primary-color': '#123456', '$secondary-color': '#654321' },
|
|
44
|
+
'usage',
|
|
45
|
+
[],
|
|
46
|
+
'forward',
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should work with short-form aliases', async () => {
|
|
51
|
+
const args = ['-p', 'styles.scss', '-s', 'variables.scss'];
|
|
52
|
+
|
|
53
|
+
await replaceVariablesCommand(args);
|
|
54
|
+
|
|
55
|
+
expect(mapSCSStoCSS).toHaveBeenCalledWith('variables.scss');
|
|
56
|
+
expect(transformInPath).toHaveBeenCalledWith(
|
|
57
|
+
'styles.scss',
|
|
58
|
+
{ '$primary-color': '#123456', '$secondary-color': '#654321' },
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle usage replacement type without direction', async () => {
|
|
63
|
+
const args = [
|
|
64
|
+
'--filePath', 'styles.scss',
|
|
65
|
+
'--source', 'variables.scss',
|
|
66
|
+
'--replacementType', 'usage',
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
await replaceVariablesCommand(args);
|
|
70
|
+
|
|
71
|
+
expect(transformInPath).toHaveBeenCalledWith(
|
|
72
|
+
'styles.scss',
|
|
73
|
+
{ '$primary-color': '#123456', '$secondary-color': '#654321' },
|
|
74
|
+
'usage',
|
|
75
|
+
[],
|
|
76
|
+
undefined,
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
|
|
3
|
+
const { sendTrackInfo, capitalize } = require('../utils');
|
|
4
|
+
|
|
5
|
+
jest.mock('axios');
|
|
6
|
+
|
|
7
|
+
describe('utils', () => {
|
|
8
|
+
describe('sendTrackInfo', () => {
|
|
9
|
+
const originalEnv = process.env;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
process.env = { ...originalEnv };
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterAll(() => {
|
|
17
|
+
process.env = originalEnv;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should send tracking data when TRACK_ANONYMOUS_ANALYTICS is true', async () => {
|
|
21
|
+
process.env.TRACK_ANONYMOUS_ANALYTICS = 'true';
|
|
22
|
+
process.env.BASE_URL = 'http://test.com';
|
|
23
|
+
const mockResponse = { status: 200 };
|
|
24
|
+
axios.post.mockResolvedValue(mockResponse);
|
|
25
|
+
|
|
26
|
+
const eventId = 'test_event';
|
|
27
|
+
const properties = { prop1: 'value1' };
|
|
28
|
+
await sendTrackInfo(eventId, properties);
|
|
29
|
+
|
|
30
|
+
expect(axios.post).toHaveBeenCalledWith(
|
|
31
|
+
'http://test.com/.netlify/functions/sendTrackData',
|
|
32
|
+
{ eventId, properties },
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should not send tracking data when TRACK_ANONYMOUS_ANALYTICS is false', async () => {
|
|
37
|
+
process.env.TRACK_ANONYMOUS_ANALYTICS = '';
|
|
38
|
+
|
|
39
|
+
await sendTrackInfo('test_event', {});
|
|
40
|
+
|
|
41
|
+
expect(axios.post).not.toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should handle errors gracefully', async () => {
|
|
45
|
+
process.env.TRACK_ANONYMOUS_ANALYTICS = 'true';
|
|
46
|
+
process.env.BASE_URL = 'http://test.com';
|
|
47
|
+
const error = new Error('Network error');
|
|
48
|
+
axios.post.mockRejectedValue(error);
|
|
49
|
+
|
|
50
|
+
const consoleSpy = jest.spyOn(console, 'log');
|
|
51
|
+
|
|
52
|
+
await sendTrackInfo('test_event', {});
|
|
53
|
+
|
|
54
|
+
// Wait for the next tick to allow promise rejection to be handled
|
|
55
|
+
await new Promise(process.nextTick);
|
|
56
|
+
|
|
57
|
+
expect(consoleSpy).toHaveBeenCalledWith('Track info request failed (Error: Network error)');
|
|
58
|
+
consoleSpy.mockRestore();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('capitalize', () => {
|
|
63
|
+
it('should capitalize the first letter of a string', () => {
|
|
64
|
+
expect(capitalize('hello')).toBe('Hello');
|
|
65
|
+
expect(capitalize('world')).toBe('World');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return empty string for empty input', () => {
|
|
69
|
+
expect(capitalize('')).toBe('');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should return empty string for non-string input', () => {
|
|
73
|
+
expect(capitalize(null)).toBe('');
|
|
74
|
+
expect(capitalize(undefined)).toBe('');
|
|
75
|
+
expect(capitalize(123)).toBe('');
|
|
76
|
+
expect(capitalize({})).toBe('');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should handle single character strings', () => {
|
|
80
|
+
expect(capitalize('a')).toBe('A');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should not modify already capitalized strings', () => {
|
|
84
|
+
expect(capitalize('Hello')).toBe('Hello');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
const { version } = require('../../package.json');
|
|
4
|
+
const versionCommand = require('../version');
|
|
5
|
+
|
|
6
|
+
console.log = jest.fn(); /* eslint-disable-line no-console */
|
|
7
|
+
|
|
8
|
+
describe('versionCommand', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
console.log.mockClear(); /* eslint-disable-line no-console */
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should log the correct version number', () => {
|
|
14
|
+
versionCommand();
|
|
15
|
+
|
|
16
|
+
expect(console.log).toHaveBeenCalledTimes(1); /* eslint-disable-line no-console */
|
|
17
|
+
const consoleOutput = console.log.mock.calls[0][0]; /* eslint-disable-line no-console */
|
|
18
|
+
expect(consoleOutput).toContain(`Paragon CLI version: ${chalk.bold(version)}`);
|
|
19
|
+
});
|
|
20
|
+
});
|
package/lib/build-scss.js
CHANGED
package/lib/help.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openedx/paragon",
|
|
3
|
-
"version": "23.5.
|
|
3
|
+
"version": "23.5.2",
|
|
4
4
|
"description": "Accessible, responsive UI component library based on Bootstrap.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"debug-test": "node --inspect-brk node_modules/.bin/jest --runInBand --coverage",
|
|
33
33
|
"stylelint": "stylelint \"src/**/*.scss\" \"scss/**/*.scss\" \"www/src/**/*.scss\" --config .stylelintrc.json",
|
|
34
34
|
"lint": "npm run stylelint && eslint --ext .js --ext .jsx --ext .ts --ext .tsx . && npm run lint --workspaces --if-present",
|
|
35
|
+
"lint:fix": "npm run stylelint && eslint --fix --ext .js --ext .jsx --ext .ts --ext .tsx . && npm run lint --workspaces --if-present",
|
|
35
36
|
"prepublishOnly": "npm run build",
|
|
36
37
|
"semantic-release": "semantic-release",
|
|
37
38
|
"snapshot": "jest --updateSnapshot",
|
|
@@ -1399,7 +1399,7 @@
|
|
|
1399
1399
|
--pgn-color-icon-button-text-black-inverse-active-focus: var(--pgn-color-icon-button-text-black-inverse-active-base);
|
|
1400
1400
|
--pgn-color-image-figure-caption: var(--pgn-color-gray-500);
|
|
1401
1401
|
--pgn-color-menu-item-color: var(--pgn-color-body-base);
|
|
1402
|
-
--pgn-color-menu-item-hover-color: var(--pgn-color-btn-text-tertiary);
|
|
1402
|
+
--pgn-color-menu-item-hover-color: var(--pgn-color-btn-hover-text-tertiary);
|
|
1403
1403
|
--pgn-color-menu-select-btn-link-color: var(--pgn-color-primary-500);
|
|
1404
1404
|
--pgn-color-modal-content-bg: var(--pgn-color-bg-base);
|
|
1405
1405
|
--pgn-color-nav-tabs-base-border-base: var(--pgn-color-light-400);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const mockStyleDictionaryInstance = {
|
|
2
|
+
cleanAllPlatforms: jest.fn().mockResolvedValue(undefined),
|
|
3
|
+
buildAllPlatforms: jest.fn().mockResolvedValue(undefined),
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const mockStyleDictionary = jest.fn(() => mockStyleDictionaryInstance);
|
|
7
|
+
|
|
8
|
+
mockStyleDictionary.hooks = {
|
|
9
|
+
transforms: {
|
|
10
|
+
'color/sass-color-functions': {},
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const mockInitializeStyleDictionary = jest.fn().mockResolvedValue(mockStyleDictionary);
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
initializeStyleDictionary: mockInitializeStyleDictionary,
|
|
18
|
+
getTokensStudioTransforms: jest.fn().mockResolvedValue({
|
|
19
|
+
expandTypesMap: {},
|
|
20
|
+
}),
|
|
21
|
+
colorTransform: jest.fn(),
|
|
22
|
+
__mockInstance: mockStyleDictionaryInstance,
|
|
23
|
+
__mockClass: mockStyleDictionary,
|
|
24
|
+
};
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"bg": { "source": "$menu-item-bg", "$value": "transparent" },
|
|
15
15
|
"border": { "source": "$menu-item-border-color", "$value": "{color.menu.item.bg}" },
|
|
16
16
|
"hover": {
|
|
17
|
-
"color": { "source": "$menu-item-hover-color", "$value": "{color.btn.text.tertiary}" },
|
|
17
|
+
"color": { "source": "$menu-item-hover-color", "$value": "{color.btn.hover.text.tertiary}" },
|
|
18
18
|
"bg": { "source": "$menu-item-hover-bg", "$value": "{color.btn.hover.bg.tertiary}" },
|
|
19
19
|
"border": { "source": "$menu-item-hover-border-color", "$value": "{color.menu.item.bg}" }
|
|
20
20
|
},
|