anubis-ui 1.2.15 → 1.2.18

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.
@@ -0,0 +1,68 @@
1
+ import { IColor } from '@interfaces/color.interface';
2
+ import { log } from '@tools/logger';
3
+
4
+ /**
5
+ * Validates a single color value (hex color or 'transparent')
6
+ */
7
+ const isValidColorValue = (value: string): boolean => {
8
+ if (value === 'transparent') {
9
+ return true;
10
+ }
11
+ // Validate hex color format (#RRGGBB or #RGB)
12
+ return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value);
13
+ };
14
+
15
+ /**
16
+ * Validates the colors configuration
17
+ * Ensures each color has at least one valid theme (light or dark)
18
+ * @throws Error if validation fails
19
+ */
20
+ const validateColors = (colors: IColor): void => {
21
+ const errors: string[] = [];
22
+
23
+ Object.entries(colors).forEach(([colorName, colorConfig]) => {
24
+ const { light, dark } = colorConfig;
25
+
26
+ // Check if at least one theme is defined
27
+ if (!light && !dark) {
28
+ errors.push(
29
+ `Color "${colorName}": must have at least one theme defined (light or dark)`
30
+ );
31
+ return;
32
+ }
33
+
34
+ // Validate light color if defined
35
+ if (light && !isValidColorValue(light)) {
36
+ errors.push(
37
+ `Color "${colorName}": invalid light color value "${light}". Expected hex format (#RRGGBB) or "transparent"`
38
+ );
39
+ }
40
+
41
+ // Validate dark color if defined
42
+ if (dark && !isValidColorValue(dark)) {
43
+ errors.push(
44
+ `Color "${colorName}": invalid dark color value "${dark}". Expected hex format (#RRGGBB) or "transparent"`
45
+ );
46
+ }
47
+ });
48
+
49
+ if (errors.length > 0) {
50
+ const errorMessage = [
51
+ '❌ Color configuration validation failed:',
52
+ ...errors.map(err => ` - ${err}`),
53
+ '',
54
+ 'Please check your colors.config.json or anubis.config.json file.',
55
+ ].join('\n');
56
+
57
+ log(errorMessage);
58
+ throw new Error('Invalid color configuration');
59
+ }
60
+
61
+ log(
62
+ `✅ Color configuration validated (${
63
+ Object.keys(colors).length
64
+ } colors)`
65
+ );
66
+ };
67
+
68
+ export { validateColors, isValidColorValue };
@@ -0,0 +1,54 @@
1
+ # AnubisUI Tests
2
+
3
+ This directory contains all tests for the AnubisUI project.
4
+
5
+ ## Running Tests
6
+
7
+ ```bash
8
+ # Run tests in watch mode (development)
9
+ npm test
10
+
11
+ # Run tests once (CI/production)
12
+ npm run test:run
13
+
14
+ # Run tests with UI
15
+ npm run test:ui
16
+ ```
17
+
18
+ ## Test Structure
19
+
20
+ ```
21
+ tests/
22
+ └── validation/
23
+ └── color.validation.test.ts # Color configuration validation tests
24
+ ```
25
+
26
+ ## Test Coverage
27
+
28
+ ### Color Validation (`color.validation.test.ts`)
29
+
30
+ Tests the color configuration validation logic to ensure:
31
+ - Valid colors pass validation
32
+ - Invalid colors are properly rejected
33
+ - Edge cases are handled correctly
34
+
35
+ **Test groups:**
36
+ 1. **Valid configurations** - Tests all supported color formats
37
+ 2. **Invalid configurations** - Ensures improper configs are rejected
38
+ 3. **Helper functions** - Tests individual validation utilities
39
+
40
+ ## Adding New Tests
41
+
42
+ 1. Create a new test file in the appropriate subdirectory
43
+ 2. Use the `.test.ts` extension
44
+ 3. Import test utilities from Vitest:
45
+ ```typescript
46
+ import { describe, it, expect } from 'vitest';
47
+ ```
48
+
49
+ ## Test Framework
50
+
51
+ We use [Vitest](https://vitest.dev/) for testing:
52
+ - **Fast** - Native ESM and TypeScript support
53
+ - **Compatible** - Jest-like API
54
+ - **Modern** - Built for Vite projects
@@ -0,0 +1,182 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ validateColors,
4
+ isValidColorValue,
5
+ } from '@validation/color.validation';
6
+ import { IColor } from '@interfaces/color.interface';
7
+
8
+ describe('isValidColorValue', () => {
9
+ it('should accept valid hex colors (#RRGGBB)', () => {
10
+ expect(isValidColorValue('#ff00ff')).toBe(true);
11
+ expect(isValidColorValue('#0f84cb')).toBe(true);
12
+ expect(isValidColorValue('#000000')).toBe(true);
13
+ expect(isValidColorValue('#ffffff')).toBe(true);
14
+ });
15
+
16
+ it('should accept valid short hex colors (#RGB)', () => {
17
+ expect(isValidColorValue('#f0f')).toBe(true);
18
+ expect(isValidColorValue('#0ff')).toBe(true);
19
+ expect(isValidColorValue('#000')).toBe(true);
20
+ expect(isValidColorValue('#fff')).toBe(true);
21
+ });
22
+
23
+ it('should accept transparent keyword', () => {
24
+ expect(isValidColorValue('transparent')).toBe(true);
25
+ });
26
+
27
+ it('should accept mixed case hex values', () => {
28
+ expect(isValidColorValue('#FF00FF')).toBe(true);
29
+ expect(isValidColorValue('#Ff00fF')).toBe(true);
30
+ });
31
+
32
+ it('should reject invalid hex colors', () => {
33
+ expect(isValidColorValue('not-a-color')).toBe(false);
34
+ expect(isValidColorValue('#ff')).toBe(false);
35
+ expect(isValidColorValue('#fffffff')).toBe(false);
36
+ expect(isValidColorValue('ff00ff')).toBe(false);
37
+ expect(isValidColorValue('#gggggg')).toBe(false);
38
+ expect(isValidColorValue('#xyz')).toBe(false);
39
+ });
40
+ });
41
+
42
+ describe('validateColors', () => {
43
+ describe('Valid configurations', () => {
44
+ it('should accept color with both light and dark themes', () => {
45
+ const colors: IColor = {
46
+ primary: { light: '#0f84cb', dark: '#1a94db' },
47
+ };
48
+ expect(() => validateColors(colors)).not.toThrow();
49
+ });
50
+
51
+ it('should accept color with only light theme', () => {
52
+ const colors: IColor = {
53
+ 'custom-light': { light: '#ff00ff' },
54
+ };
55
+ expect(() => validateColors(colors)).not.toThrow();
56
+ });
57
+
58
+ it('should accept color with only dark theme', () => {
59
+ const colors: IColor = {
60
+ 'custom-dark': { dark: '#00ffff' },
61
+ };
62
+ expect(() => validateColors(colors)).not.toThrow();
63
+ });
64
+
65
+ it('should accept transparent colors', () => {
66
+ const colors: IColor = {
67
+ none: { light: 'transparent', dark: 'transparent' },
68
+ };
69
+ expect(() => validateColors(colors)).not.toThrow();
70
+ });
71
+
72
+ it('should accept short hex format (#RGB)', () => {
73
+ const colors: IColor = {
74
+ 'short-hex': { light: '#f0f', dark: '#0ff' },
75
+ };
76
+ expect(() => validateColors(colors)).not.toThrow();
77
+ });
78
+
79
+ it('should accept multiple valid colors', () => {
80
+ const colors: IColor = {
81
+ primary: { light: '#0f84cb', dark: '#1a94db' },
82
+ secondary: { light: '#3b5161', dark: '#4a5f6f' },
83
+ accent: { light: '#0f84cb' },
84
+ };
85
+ expect(() => validateColors(colors)).not.toThrow();
86
+ });
87
+
88
+ it('should accept empty colors object', () => {
89
+ const colors: IColor = {};
90
+ expect(() => validateColors(colors)).not.toThrow();
91
+ });
92
+
93
+ it('should accept uppercase hex values', () => {
94
+ const colors: IColor = {
95
+ uppercase: { light: '#FF00FF', dark: '#00FFFF' },
96
+ };
97
+ expect(() => validateColors(colors)).not.toThrow();
98
+ });
99
+
100
+ it('should accept mixed case hex values', () => {
101
+ const colors: IColor = {
102
+ mixed: { light: '#Ff00fF', dark: '#00FfFf' },
103
+ };
104
+ expect(() => validateColors(colors)).not.toThrow();
105
+ });
106
+ });
107
+
108
+ describe('Invalid configurations', () => {
109
+ it('should reject empty color object (no light or dark)', () => {
110
+ const colors: IColor = {
111
+ 'test-empty': {},
112
+ };
113
+ expect(() => validateColors(colors)).toThrow(
114
+ 'Invalid color configuration'
115
+ );
116
+ });
117
+
118
+ it('should reject invalid hex color format', () => {
119
+ const colors: IColor = {
120
+ 'test-invalid': { light: 'not-a-color' },
121
+ };
122
+ expect(() => validateColors(colors)).toThrow(
123
+ 'Invalid color configuration'
124
+ );
125
+ });
126
+
127
+ it('should reject invalid short hex (#XX)', () => {
128
+ const colors: IColor = {
129
+ 'test-short': { light: '#ff' },
130
+ };
131
+ expect(() => validateColors(colors)).toThrow(
132
+ 'Invalid color configuration'
133
+ );
134
+ });
135
+
136
+ it('should reject hex color without #', () => {
137
+ const colors: IColor = {
138
+ 'test-no-hash': { light: 'ff00ff' },
139
+ };
140
+ expect(() => validateColors(colors)).toThrow(
141
+ 'Invalid color configuration'
142
+ );
143
+ });
144
+
145
+ it('should reject invalid characters in hex', () => {
146
+ const colors: IColor = {
147
+ 'test-invalid-chars': { light: '#gggggg' },
148
+ };
149
+ expect(() => validateColors(colors)).toThrow(
150
+ 'Invalid color configuration'
151
+ );
152
+ });
153
+
154
+ it('should reject when both light and dark are invalid', () => {
155
+ const colors: IColor = {
156
+ 'test-both-invalid': { light: 'invalid', dark: 'also-invalid' },
157
+ };
158
+ expect(() => validateColors(colors)).toThrow(
159
+ 'Invalid color configuration'
160
+ );
161
+ });
162
+
163
+ it('should reject when mixing valid and invalid colors', () => {
164
+ const colors: IColor = {
165
+ valid: { light: '#ff00ff' },
166
+ invalid: { light: 'not-valid' },
167
+ };
168
+ expect(() => validateColors(colors)).toThrow(
169
+ 'Invalid color configuration'
170
+ );
171
+ });
172
+
173
+ it('should reject when only dark color is invalid', () => {
174
+ const colors: IColor = {
175
+ 'test-dark-invalid': { light: '#ff00ff', dark: 'not-valid' },
176
+ };
177
+ expect(() => validateColors(colors)).toThrow(
178
+ 'Invalid color configuration'
179
+ );
180
+ });
181
+ });
182
+ });
package/tsconfig.json CHANGED
@@ -6,8 +6,17 @@
6
6
  "module": "commonjs",
7
7
  "target": "es2019",
8
8
  "skipLibCheck": true, // Ignore la vérification des fichiers .d.ts dans node_modules
9
- "esModuleInterop": true
9
+ "esModuleInterop": true,
10
+ "resolveJsonModule": true,
11
+ "baseUrl": "./src",
12
+ "paths": {
13
+ "@/*": ["./*"],
14
+ "@config/*": ["./config/*"],
15
+ "@interfaces/*": ["./interfaces/*"],
16
+ "@tools/*": ["./tools/*"],
17
+ "@validation/*": ["./tools/validation/*"]
18
+ }
10
19
  },
11
- "include": ["src/**/*"],
20
+ "include": ["src/**/*", "package.json"],
12
21
  "exclude": ["node_modules", "dist"]
13
22
  }
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import path from 'path';
3
+
4
+ export default defineConfig({
5
+ resolve: {
6
+ alias: {
7
+ '@': path.resolve(__dirname, './src'),
8
+ '@config': path.resolve(__dirname, './src/config'),
9
+ '@interfaces': path.resolve(__dirname, './src/interfaces'),
10
+ '@tools': path.resolve(__dirname, './src/tools'),
11
+ '@validation': path.resolve(__dirname, './src/tools/validation'),
12
+ },
13
+ },
14
+ test: {
15
+ globals: true,
16
+ environment: 'node',
17
+ include: ['tests/**/*.test.ts'],
18
+ },
19
+ });
@@ -1,51 +0,0 @@
1
- [
2
- {
3
- "prefix": "bg",
4
- "declaration": "background: ${color}"
5
- },
6
- {
7
- "prefix": "text",
8
- "declaration": "color: ${color}"
9
- },
10
- {
11
- "prefix": "border",
12
- "declaration": "border-width: ${value} !important; border-color: ${color} !important; border-style: solid;",
13
- "variations": {
14
- "default": "4px",
15
- "thinest": "1px",
16
- "thiner": "2px",
17
- "thin": "3px",
18
- "thick": "6px",
19
- "thicker": "8px",
20
- "thickest": "10px",
21
- "node": "0.2rem"
22
- }
23
- },
24
- {
25
- "prefix": "inner-border",
26
- "declaration": "box-shadow: inset 0px 0px 0px ${value} ${color}",
27
- "variations": {
28
- "default": "4px" ,
29
- "thinest": "1px" ,
30
- "thiner": "2px" ,
31
- "thin2": "3px" ,
32
- "thick": "6px" ,
33
- "thicker": "8px" ,
34
- "thickest": "10px" ,
35
- "node": "0.2rem"
36
- }
37
- },
38
- {
39
- "prefix": "shadow",
40
- "declaration": "box-shadow: ${value} ${color}",
41
- "variations": {
42
- "default": "0px 0px 7px 1px" ,
43
- "densest": "0px 0px 3px 1px" ,
44
- "lower": "0px 0px 5px 1px" ,
45
- "dense": "0px 0px 5px 1px" ,
46
- "wide": "0px 0px 10px 1px" ,
47
- "wider": "0px 0px 15px 1px" ,
48
- "widest": "0px 0px 20px 1px"
49
- }
50
- }
51
- ]
@@ -1,78 +0,0 @@
1
- [
2
- {
3
- "prefix": "blur",
4
- "standalone": true,
5
- "declaration": "backdrop-filter: blur(${value})",
6
- "variations": {
7
- "default": "3px"
8
- }
9
- },
10
- {
11
- "prefix": "smooth",
12
- "standalone": true,
13
- "declaration": "transition-duration: ${value}",
14
- "variations": {
15
- "default": "0.1s",
16
- "slowest": "0.5s",
17
- "slower": "0.3s",
18
- "slow": "0.2s",
19
- "quick": "0.07s",
20
- "quicker": "0.05s",
21
- "quickest": "0.03s"
22
- }
23
- },
24
- {
25
- "prefix": "rounded",
26
- "standalone": true,
27
- "declaration": "border-radius: ${value}",
28
- "variations": {
29
- "default": "8px",
30
- "square": "0px",
31
- "xs": "2px",
32
- "sm": "4px",
33
- "md": "8px",
34
- "lg": "12px",
35
- "xl": "16px",
36
- "very": "9999px",
37
- "full": "50%",
38
- "half": "100%"
39
- }
40
- },
41
- {
42
- "prefix": "border",
43
- "declaration": "border-style: ${value}",
44
- "variations": {
45
- "solid": "solid",
46
- "dashed": "dashed",
47
- "dotted": "dotted"
48
- }
49
- },
50
- {
51
- "prefix": "position",
52
- "declaration": "position: ${value}",
53
- "variations": {
54
- "relative": "relative",
55
- "absolute": "absolute"
56
- }
57
- },
58
- {
59
- "prefix": "size",
60
- "declaration": "font-size: ${value} !important",
61
- "variations": {
62
- "2xs": "10px",
63
- "xs": "12px",
64
- "sm": "14px",
65
- "md": "16px",
66
- "lg": "18px",
67
- "xl": "20px",
68
- "2xl": "24px",
69
- "3xl": "30px",
70
- "4xl": "36px",
71
- "5xl": "48px",
72
- "6xl": "60px",
73
- "7xl": "72px",
74
- "8xl": "96px",
75
- "9xl": "128px"
76
- }
77
- }
78
- ]
@@ -1,10 +0,0 @@
1
- export interface IDeclaration {
2
- /** Color variable name, defined in the DOM :root */
3
- name: string
4
-
5
- /** Hexadecimal color code */
6
- color: string|number
7
-
8
- // light?: string
9
- // dark?: string
10
- }
@@ -1,26 +0,0 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
-
4
- import { log } from '../logger'
5
-
6
- const userConfigPath = path.join(process.cwd(), 'anubis.config.json')
7
- let userConfig = null
8
-
9
- const readUserConfigFile = () => {
10
- const userConfigExists = fs.existsSync(userConfigPath)
11
-
12
- if (!userConfigExists) {
13
- log('No user config file found, using default configuration.')
14
- return
15
- }
16
-
17
- const config = fs.readFileSync(userConfigPath, { encoding: 'utf-8'})
18
- userConfig = JSON.parse(config)
19
-
20
- return userConfig
21
- }
22
-
23
- export {
24
- userConfig,
25
- readUserConfigFile
26
- }
@@ -1,37 +0,0 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
-
4
- import { cssHeader, log } from '../logger'
5
-
6
- const srcDir = path.join(process.cwd(), 'src', 'css')
7
- const outputPath = path.join(srcDir, '_anubis.scss')
8
-
9
- const checkCssRuleFilePresence = () => {
10
- try {
11
- fs.mkdirSync(srcDir, { recursive: true })
12
-
13
- if (fs.existsSync(outputPath)) { return }
14
-
15
- log('Output file missing, generating..')
16
- fs.writeFileSync(outputPath, '')
17
- } catch (err: any) {
18
- throw new Error(`Erreur lors de la vérification du fichier CSS: ${err.message}`)
19
- }
20
- }
21
-
22
- const buildCssRuleFile = (classes: string = '') => {
23
- try {
24
- checkCssRuleFilePresence()
25
-
26
- fs.writeFileSync(outputPath, cssHeader + '\n' + classes)
27
-
28
- return outputPath
29
- } catch (err: any) {
30
- throw new Error(`Erreur lors de l'écriture du fichier CSS: ${err.message}`)
31
- }
32
- }
33
-
34
- export {
35
- checkCssRuleFilePresence,
36
- buildCssRuleFile
37
- }